├── .editorconfig ├── .eslintignore ├── .eslintrc.yml ├── .gitattributes ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── README.md ├── app ├── USAGE ├── config.js ├── index.js └── templates │ ├── _package.json │ ├── apple-touch-icon.png │ ├── babelrc │ ├── editorconfig │ ├── favicon.ico │ ├── gitattributes │ ├── gitignore │ ├── gulpfile.js │ ├── index.html │ ├── main.css │ ├── main.js │ ├── main.scss │ ├── modernizr.json │ └── robots.txt ├── contributing.md ├── docs ├── README.md ├── faq.md └── recipes │ ├── README.md │ ├── asset-revisioning.md │ ├── aws-s3-deployment.md │ ├── browserify.md │ ├── coffeescript.md │ ├── gh-pages.md │ ├── handlebars.md │ ├── htmlhint.md │ ├── less.md │ ├── liquify.md │ ├── node-heroku.md │ ├── nunjucks.md │ ├── pug.md │ ├── react-router.md │ ├── react.md │ ├── rsync-deploy.md │ └── stylus.md ├── package.json ├── screenshot.png ├── test ├── bootstrap.js ├── general.js ├── google_analytics.js ├── jquery.js ├── modernizr.js ├── sass.js ├── slug.js └── test-framework.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /app/templates 2 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | parserOptions: 2 | ecmaVersion: 6 3 | 4 | env: 5 | node: true 6 | 7 | plugins: 8 | - prettier 9 | 10 | extends: 11 | - eslint:recommended 12 | - prettier 13 | 14 | rules: 15 | prettier/prettier: error 16 | 17 | overrides: 18 | - files: "test/**/*.js" 19 | env: 20 | mocha: true 21 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | name: Lint and test on ${{ matrix.os }} with Node.js ${{ matrix.node }} 8 | strategy: 9 | matrix: 10 | os: [ubuntu-latest, windows-latest] 11 | node: [8, 10, 12] 12 | # run only one instance of Windows as a sanity check 13 | exclude: 14 | - 15 | os: windows-latest 16 | node: 8 17 | - 18 | os: windows-latest 19 | node: 10 20 | runs-on: ${{ matrix.os }} 21 | steps: 22 | - 23 | uses: actions/checkout@v2 24 | - 25 | uses: actions/setup-node@v1 26 | with: 27 | node-version: ${{ matrix.node }} 28 | - 29 | name: Run scripts 30 | run: | 31 | yarn 32 | yarn lint 33 | yarn test --colors 34 | env: 35 | CI: true 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Web app generator [![Build Status](https://secure.travis-ci.org/yeoman/generator-webapp.svg?branch=master)](http://travis-ci.org/yeoman/generator-webapp) [![Gitter](https://img.shields.io/badge/Gitter-Join_the_Yeoman_chat_%E2%86%92-00d06f.svg)](https://gitter.im/yeoman/yeoman) 2 | 3 | > [Yeoman](http://yeoman.io) generator that scaffolds out a front-end web app using [gulp](http://gulpjs.com/) for the build process 4 | 5 | ![](screenshot.png) 6 | 7 | --- 8 | 9 | 🚧 There is a pre-release version of this generator, you can install it by running `npm install --global generator-webapp@next`. Help us make it stable by reporting bugs! 🚧 10 | 11 | --- 12 | 13 | 14 | ## Features 15 | 16 | Please see our [gulpfile](app/templates/gulpfile.js) for up to date information on what we support. 17 | 18 | * enable [ES2015 features](https://babeljs.io/docs/learn-es2015/) using [Babel](https://babeljs.io) 19 | * CSS Autoprefixing 20 | * Built-in preview server with BrowserSync 21 | * Automagically compile Sass with [libsass](http://libsass.org) 22 | * Automagically lint your scripts 23 | * Map compiled CSS to source stylesheets with source maps 24 | * Awesome image optimization 25 | 26 | *For more information on what this generator can do for you, take a look at the [gulp plugins](app/templates/_package.json) used in our `package.json`.* 27 | 28 | 29 | ## libsass 30 | 31 | Keep in mind that libsass is feature-wise not fully compatible with Ruby Sass. Check out [this](http://sass-compatibility.github.io) curated list of incompatibilities to find out which features are missing. 32 | 33 | If your favorite feature is missing and you really need Ruby Sass, you can always switch to [gulp-ruby-sass](https://github.com/sindresorhus/gulp-ruby-sass) and update the `styles` task in gulpfile accordingly. 34 | 35 | 36 | ## Getting Started 37 | 38 | - Install: `npm install --global yo gulp-cli generator-webapp` 39 | - Run `yo webapp` to scaffold your webapp 40 | - Run `npm start` to preview and watch for changes 41 | - Run `npm start -- --port=8080` to preview and watch for changes in port `8080` 42 | - Run `npm install --save ` to install dependencies, frontend included 43 | - Run `npm run serve:test` to run the tests in the browser 44 | - Run `npm run serve:test -- --port=8085` to run the tests in the browser in port `8085` 45 | - Run `npm run build` to build your webapp for production 46 | - Run `npm run serve:dist` to preview the production build 47 | - Run `npm run serve:dist -- --port=5000` to preview the production build in port `5000` 48 | 49 | 50 | ## Docs 51 | 52 | * [getting started](docs/README.md) with this generator 53 | * [recipes](docs/recipes/README.md) for integrating other popular technologies like CoffeeScript 54 | * [contribution](contributing.md) docs and [FAQ](docs/faq.md), good to check before posting an issue 55 | 56 | 57 | ## Options 58 | 59 | - `--skip-welcome-message` 60 | Skips Yeoman's greeting before displaying options. 61 | - `--skip-install-message` 62 | Skips the the message displayed after scaffolding has finished and before the dependencies are being installed. 63 | - `--skip-install` 64 | Doesn't automatically install dependencies after scaffolding has finished. 65 | - `--test-framework=` 66 | Either `mocha` or `jasmine`. Defaults to `mocha`. 67 | 68 | 69 | ## Contribute 70 | 71 | See the [contributing docs](contributing.md). 72 | 73 | 74 | ## Sponsors 75 | Love Yeoman work and community? Help us keep it alive by donating funds to cover project expenses!
76 | [[Become a sponsor](https://opencollective.com/yeoman#support)] 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | ## License 171 | 172 | [BSD license](http://opensource.org/licenses/bsd-license.php) 173 | -------------------------------------------------------------------------------- /app/USAGE: -------------------------------------------------------------------------------- 1 | Description: 2 | Creates a new basic front-end web application. 3 | 4 | Options: 5 | Sass: Include Sass 6 | Bootstrap: Include Bootstrap or Bootstrap Sass if Sass is included 7 | Modernizr: Include Modernizr 8 | Babel: Use ES2015 features by using Babel 9 | 10 | Example: 11 | yo webapp 12 | 13 | This will create: 14 | gulpfile.js: Configuration for the task runner. 15 | package.json: Development packages installed by npm. 16 | 17 | app/: Your application files. 18 | test/: Unit tests for your application. 19 | -------------------------------------------------------------------------------- /app/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | options: { 3 | 'skip-welcome-message': { 4 | desc: 'Skips the welcome message', 5 | type: Boolean 6 | }, 7 | 'skip-install-message': { 8 | desc: 'Skips the message after the installation of dependencies', 9 | type: Boolean 10 | }, 11 | 'test-framework': { 12 | desc: 'Test framework to be invoked', 13 | type: String, 14 | defaults: 'mocha' 15 | }, 16 | 'skip-test-framework': { 17 | desc: `Skips adding the test framework`, 18 | type: Boolean 19 | } 20 | }, 21 | prompts: [ 22 | { 23 | type: 'checkbox', 24 | name: 'features', 25 | message: 'Which additional features would you like to include?', 26 | choices: [ 27 | { 28 | name: 'Sass', 29 | value: 'includeSass', 30 | checked: true 31 | }, 32 | { 33 | name: 'Bootstrap', 34 | value: 'includeBootstrap', 35 | checked: true 36 | }, 37 | { 38 | name: 'Modernizr', 39 | value: 'includeModernizr', 40 | checked: true 41 | }, 42 | { 43 | name: 'Google Analytics', 44 | value: 'includeAnalytics', 45 | checked: true 46 | } 47 | ] 48 | }, 49 | { 50 | type: 'confirm', 51 | name: 'includeJQuery', 52 | message: 'Would you like to include jQuery?', 53 | default: true, 54 | when: answers => !answers.features.includes('includeBootstrap') 55 | } 56 | ], 57 | dirsToCreate: ['app/images', 'app/fonts'], 58 | filesToCopy: [ 59 | { 60 | input: 'babelrc', 61 | output: '.babelrc' 62 | }, 63 | { 64 | input: 'gitignore', 65 | output: '.gitignore' 66 | }, 67 | { 68 | input: 'gitattributes', 69 | output: '.gitattributes' 70 | }, 71 | { 72 | input: 'editorconfig', 73 | output: '.editorconfig' 74 | }, 75 | { 76 | input: 'favicon.ico', 77 | output: 'app/favicon.ico' 78 | }, 79 | { 80 | input: 'apple-touch-icon.png', 81 | output: 'app/apple-touch-icon.png' 82 | }, 83 | { 84 | input: 'robots.txt', 85 | output: 'app/robots.txt' 86 | } 87 | ], 88 | filesToRender: [ 89 | { 90 | input: 'gulpfile.js', 91 | output: 'gulpfile.js' 92 | }, 93 | { 94 | input: '_package.json', 95 | output: 'package.json' 96 | }, 97 | { 98 | input: 'main.js', 99 | output: 'app/scripts/main.js' 100 | }, 101 | { 102 | input: 'index.html', 103 | output: 'app/index.html' 104 | } 105 | ] 106 | }; 107 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const Generator = require('yeoman-generator'); 3 | const commandExists = require('command-exists').sync; 4 | const yosay = require('yosay'); 5 | const mkdirp = require('mkdirp'); 6 | const config = require('./config'); 7 | 8 | module.exports = class extends Generator { 9 | constructor(args, opts) { 10 | super(args, opts); 11 | for (let optionName in config.options) { 12 | this.option(optionName, config.options[optionName]); 13 | } 14 | } 15 | 16 | initializing() { 17 | this.pkg = require('../package.json'); 18 | 19 | if (this.options['skip-test-framework']) { 20 | return; 21 | } 22 | 23 | this.composeWith( 24 | require.resolve( 25 | `generator-${this.options['test-framework']}/generators/app` 26 | ), 27 | { 28 | 'skip-install': this.options['skip-install'] 29 | } 30 | ); 31 | } 32 | 33 | prompting() { 34 | if (!this.options['skip-welcome-message']) { 35 | this.log( 36 | yosay( 37 | "'Allo 'allo! Out of the box I include HTML5 Boilerplate, jQuery, and a gulpfile to build your app." 38 | ) 39 | ); 40 | } 41 | 42 | return this.prompt(config.prompts).then(answers => { 43 | const features = answers.features; 44 | const hasFeature = feat => features && features.includes(feat); 45 | 46 | // manually deal with the response, get back and store the results. 47 | // we change a bit this way of doing to automatically do this in the self.prompt() method. 48 | this.includeSass = hasFeature('includeSass'); 49 | this.includeBootstrap = hasFeature('includeBootstrap'); 50 | this.includeModernizr = hasFeature('includeModernizr'); 51 | this.includeAnalytics = hasFeature('includeAnalytics'); 52 | this.includeJQuery = answers.includeJQuery; 53 | }); 54 | } 55 | 56 | writing() { 57 | const templateData = { 58 | appname: this.appname, 59 | date: new Date().toISOString().split('T')[0], 60 | name: this.pkg.name, 61 | version: this.pkg.version, 62 | includeSass: this.includeSass, 63 | includeBootstrap: this.includeBootstrap, 64 | testFramework: this.options['test-framework'], 65 | includeJQuery: this.includeJQuery, 66 | includeModernizr: this.includeModernizr, 67 | includeAnalytics: this.includeAnalytics 68 | }; 69 | 70 | const copy = (input, output) => { 71 | this.fs.copy(this.templatePath(input), this.destinationPath(output)); 72 | }; 73 | 74 | const copyTpl = (input, output, data) => { 75 | this.fs.copyTpl( 76 | this.templatePath(input), 77 | this.destinationPath(output), 78 | data 79 | ); 80 | }; 81 | 82 | // Render Files 83 | config.filesToRender.forEach(file => { 84 | copyTpl(file.input, file.output, templateData); 85 | }); 86 | 87 | // Copy Files 88 | config.filesToCopy.forEach(file => { 89 | copy(file.input, file.output); 90 | }); 91 | 92 | // Create extra directories 93 | config.dirsToCreate.forEach(item => { 94 | mkdirp(item); 95 | }); 96 | 97 | if (this.includeModernizr) { 98 | copy('modernizr.json', 'modernizr.json'); 99 | } 100 | 101 | let cssFile = `main.${this.includeSass ? 'scss' : 'css'}`; 102 | copyTpl(cssFile, `app/styles/${cssFile}`, templateData); 103 | } 104 | 105 | install() { 106 | const hasYarn = commandExists('yarn'); 107 | this.installDependencies({ 108 | npm: !hasYarn, 109 | yarn: hasYarn, 110 | bower: false, 111 | skipMessage: this.options['skip-install-message'], 112 | skipInstall: this.options['skip-install'] 113 | }); 114 | } 115 | }; 116 | -------------------------------------------------------------------------------- /app/templates/_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "engines": { 4 | "node": ">=4" 5 | }, 6 | "dependencies": { 7 | <%_ if (includeBootstrap) { -%> 8 | "bootstrap": "^4.3.1", 9 | <%_ } if (includeBootstrap || includeJQuery) { -%> 10 | "jquery": "^3.4.1"<% if (includeBootstrap || includeModernizr) { %>,<% } %> 11 | <%_ } if (includeModernizr) { -%> 12 | "modernizr": "^3.7.1"<% if (includeBootstrap) { %>,<% } %> 13 | <%_ } if (includeBootstrap) { -%> 14 | "popper.js": "^1.15.0" 15 | <%_ } -%> 16 | }, 17 | "devDependencies": { 18 | "@babel/core": "^7.4.5", 19 | "@babel/preset-env": "^7.4.5", 20 | "autoprefixer": "^9.5.1", 21 | "browser-sync": "^2.26.5", 22 | "cross-env": "^5.2.0", 23 | "cssnano": "^4.1.10", 24 | "del": "^4.1.1", 25 | "gulp": "^4.0.2", 26 | "gulp-babel": "^8.0.0", 27 | "gulp-cli": "^2.2.0", 28 | "gulp-eslint": "^5.0.0", 29 | <%_ if (includeSass) { -%> 30 | "gulp-filter": "^6.0.0", 31 | <%_ } -%> 32 | "gulp-htmlmin": "^5.0.1", 33 | "gulp-if": "^2.0.2", 34 | "gulp-imagemin": "^6.0.0", 35 | "gulp-load-plugins": "^1.6.0", 36 | "gulp-plumber": "^1.2.1", 37 | "gulp-postcss": "^8.0.0", 38 | <%_ if (includeSass) { -%> 39 | "gulp-sass": "^5.1.0", 40 | <%_ } -%> 41 | "gulp-size": "^3.0.0", 42 | "gulp-uglify": "^3.0.2", 43 | "gulp-useref": "^3.1.6", 44 | <%_ if (includeModernizr) { -%> 45 | "mkdirp": "^0.5.1", 46 | <%_ } -%> 47 | "mocha": "^6.1.4", 48 | <%_ if (includeSass) { -%> 49 | "sass": "^1.58.3", 50 | <%_ } -%> 51 | "yargs": "13.2.4" 52 | }, 53 | "scripts": { 54 | "serve:test": "cross-env NODE_ENV=test gulp serve", 55 | "serve:dist": "cross-env NODE_ENV=production gulp serve", 56 | "start": "gulp serve", 57 | "build": "cross-env NODE_ENV=production gulp", 58 | "test": "npm run serve:test", 59 | "tasks": "gulp --tasks" 60 | }, 61 | "browserslist": [ 62 | "> 1%", 63 | "last 2 versions", 64 | "Firefox ESR" 65 | ], 66 | "eslintConfig": { 67 | "parserOptions": { 68 | "sourceType": "module" 69 | }, 70 | "env": { 71 | "es6": true, 72 | "node": true, 73 | "browser": true<% if (includeJQuery || includeBootstrap) { %>, 74 | "jquery": true<% } %> 75 | }, 76 | "rules": { 77 | "quotes": [ 78 | 2, 79 | "single" 80 | ] 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /app/templates/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeoman/generator-webapp/afac1ce6ccdbe3cd2f72b1a12700b79152c9c2c3/app/templates/apple-touch-icon.png -------------------------------------------------------------------------------- /app/templates/babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /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 | 23 | [package.json] 24 | indent_style = space 25 | indent_size = 2 26 | -------------------------------------------------------------------------------- /app/templates/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeoman/generator-webapp/afac1ce6ccdbe3cd2f72b1a12700b79152c9c2c3/app/templates/favicon.ico -------------------------------------------------------------------------------- /app/templates/gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /app/templates/gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | bower_components 4 | 5 | # compiled output 6 | /dist 7 | /.tmp 8 | 9 | # System Files 10 | .DS_Store 11 | Thumbs.db 12 | -------------------------------------------------------------------------------- /app/templates/gulpfile.js: -------------------------------------------------------------------------------- 1 | // generated on <%= date %> using <%= name %> <%= version %> 2 | const { src, dest, watch, series, parallel, lastRun } = require('gulp'); 3 | const gulpLoadPlugins = require('gulp-load-plugins'); 4 | <%_ if (includeModernizr) { -%> 5 | const fs = require('fs'); 6 | const mkdirp = require('mkdirp'); 7 | const Modernizr = require('modernizr'); 8 | <%_ } -%> 9 | const browserSync = require('browser-sync'); 10 | const del = require('del'); 11 | const autoprefixer = require('autoprefixer'); 12 | <%_ if (includeSass) { -%> 13 | const sass = require('gulp-sass')(require('sass')); 14 | <%_ } -%> 15 | const cssnano = require('cssnano'); 16 | const { argv } = require('yargs'); 17 | 18 | const $ = gulpLoadPlugins(); 19 | const server = browserSync.create(); 20 | 21 | const port = argv.port || 9000; 22 | 23 | const isProd = process.env.NODE_ENV === 'production'; 24 | const isTest = process.env.NODE_ENV === 'test'; 25 | const isDev = !isProd && !isTest; 26 | 27 | function styles() { 28 | <%_ if (includeSass) { -%> 29 | return src('app/styles/*.scss', { 30 | sourcemaps: !isProd, 31 | }) 32 | .pipe($.plumber()) 33 | .pipe(sass.sync({ 34 | outputStyle: 'expanded', 35 | precision: 10, 36 | includePaths: ['.'] 37 | }).on('error', sass.logError)) 38 | <%_ } else { -%> 39 | return src('app/styles/*.css', { 40 | sourcemaps: !isProd, 41 | }) 42 | <%_ } -%> 43 | .pipe($.postcss([ 44 | autoprefixer() 45 | ])) 46 | .pipe(dest('.tmp/styles', { 47 | sourcemaps: !isProd, 48 | })) 49 | .pipe(server.reload({stream: true})); 50 | }; 51 | 52 | function scripts() { 53 | return src('app/scripts/**/*.js', { 54 | sourcemaps: !isProd, 55 | }) 56 | .pipe($.plumber()) 57 | .pipe($.babel()) 58 | .pipe(dest('.tmp/scripts', { 59 | sourcemaps: !isProd ? '.' : false, 60 | })) 61 | .pipe(server.reload({stream: true})); 62 | }; 63 | 64 | <%_ if (includeModernizr) { -%> 65 | async function modernizr() { 66 | const readConfig = () => new Promise((resolve, reject) => { 67 | fs.readFile(`${__dirname}/modernizr.json`, 'utf8', (err, data) => { 68 | if (err) reject(err); 69 | resolve(JSON.parse(data)); 70 | }) 71 | }) 72 | const createDir = () => new Promise((resolve, reject) => { 73 | mkdirp(`${__dirname}/.tmp/scripts`, err => { 74 | if (err) reject(err); 75 | resolve(); 76 | }) 77 | }); 78 | const generateScript = config => new Promise((resolve, reject) => { 79 | Modernizr.build(config, content => { 80 | fs.writeFile(`${__dirname}/.tmp/scripts/modernizr.js`, content, err => { 81 | if (err) reject(err); 82 | resolve(content); 83 | }); 84 | }) 85 | }); 86 | 87 | const [config] = await Promise.all([ 88 | readConfig(), 89 | createDir() 90 | ]); 91 | await generateScript(config); 92 | } 93 | <%_ } -%> 94 | 95 | const lintBase = (files, options) => { 96 | return src(files) 97 | .pipe($.eslint(options)) 98 | .pipe(server.reload({stream: true, once: true})) 99 | .pipe($.eslint.format()) 100 | .pipe($.if(!server.active, $.eslint.failAfterError())); 101 | } 102 | function lint() { 103 | return lintBase('app/scripts/**/*.js', { fix: true }) 104 | .pipe(dest('app/scripts')); 105 | }; 106 | function lintTest() { 107 | return lintBase('test/spec/**/*.js'); 108 | }; 109 | 110 | function html() { 111 | return src('app/*.html') 112 | .pipe($.useref({searchPath: ['.tmp', 'app', '.']})) 113 | .pipe($.if(/\.js$/, $.uglify({compress: {drop_console: true}}))) 114 | .pipe($.if(/\.css$/, $.postcss([cssnano({safe: true, autoprefixer: false})]))) 115 | .pipe($.if(/\.html$/, $.htmlmin({ 116 | collapseWhitespace: true, 117 | minifyCSS: true, 118 | minifyJS: {compress: {drop_console: true}}, 119 | processConditionalComments: true, 120 | removeComments: true, 121 | removeEmptyAttributes: true, 122 | removeScriptTypeAttributes: true, 123 | removeStyleLinkTypeAttributes: true 124 | }))) 125 | .pipe(dest('dist')); 126 | } 127 | 128 | function images() { 129 | return src('app/images/**/*', { since: lastRun(images) }) 130 | .pipe($.imagemin()) 131 | .pipe(dest('dist/images')); 132 | }; 133 | 134 | function fonts() { 135 | return src('app/fonts/**/*.{eot,svg,ttf,woff,woff2}') 136 | .pipe($.if(!isProd, dest('.tmp/fonts'), dest('dist/fonts'))); 137 | }; 138 | 139 | function extras() { 140 | return src([ 141 | 'app/*', 142 | '!app/*.html' 143 | ], { 144 | dot: true 145 | }).pipe(dest('dist')); 146 | }; 147 | 148 | function clean() { 149 | return del(['.tmp', 'dist']) 150 | } 151 | 152 | function measureSize() { 153 | return src('dist/**/*') 154 | .pipe($.size({title: 'build', gzip: true})); 155 | } 156 | 157 | const build = series( 158 | clean, 159 | parallel( 160 | lint, 161 | <%_ if (includeModernizr) { -%> 162 | series(parallel(styles, scripts, modernizr), html), 163 | <%_ } else { -%> 164 | series(parallel(styles, scripts), html), 165 | <%_ } -%> 166 | images, 167 | fonts, 168 | extras 169 | ), 170 | measureSize 171 | ); 172 | 173 | function startAppServer() { 174 | server.init({ 175 | notify: false, 176 | port, 177 | server: { 178 | baseDir: ['.tmp', 'app'], 179 | routes: { 180 | '/node_modules': 'node_modules' 181 | } 182 | } 183 | }); 184 | 185 | watch([ 186 | 'app/*.html', 187 | 'app/images/**/*', 188 | '.tmp/fonts/**/*' 189 | ]).on('change', server.reload); 190 | 191 | <%_ if (includeSass) { -%> 192 | watch('app/styles/**/*.scss', styles); 193 | <%_ } else { -%> 194 | watch('app/styles/**/*.css', styles); 195 | <%_ } -%> 196 | watch('app/scripts/**/*.js', scripts); 197 | <%_ if (includeModernizr) { -%> 198 | watch('modernizr.json', modernizr); 199 | <%_ } -%> 200 | watch('app/fonts/**/*', fonts); 201 | } 202 | 203 | function startTestServer() { 204 | server.init({ 205 | notify: false, 206 | port, 207 | ui: false, 208 | server: { 209 | baseDir: 'test', 210 | routes: { 211 | '/scripts': '.tmp/scripts', 212 | '/node_modules': 'node_modules' 213 | } 214 | } 215 | }); 216 | 217 | watch('test/index.html').on('change', server.reload); 218 | watch('app/scripts/**/*.js', scripts); 219 | watch('test/spec/**/*.js', lintTest); 220 | } 221 | 222 | function startDistServer() { 223 | server.init({ 224 | notify: false, 225 | port, 226 | server: { 227 | baseDir: 'dist', 228 | routes: { 229 | '/node_modules': 'node_modules' 230 | } 231 | } 232 | }); 233 | } 234 | 235 | let serve; 236 | if (isDev) { 237 | <%_ if (includeModernizr) { -%> 238 | serve = series(clean, parallel(styles, scripts, modernizr, fonts), startAppServer); 239 | <%_ } else { -%> 240 | serve = series(clean, parallel(styles, scripts, fonts), startAppServer); 241 | <%_ } -%> 242 | } else if (isTest) { 243 | serve = series(clean, scripts, startTestServer); 244 | } else if (isProd) { 245 | serve = series(build, startDistServer); 246 | } 247 | 248 | exports.serve = serve; 249 | exports.build = build; 250 | exports.default = build; 251 | -------------------------------------------------------------------------------- /app/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | <%_ if (includeModernizr) { -%> 3 | 4 | <%_ } else { -%> 5 | 6 | <%_ } -%> 7 | 8 | 9 | 10 | <%_ if (includeBootstrap) { %> 11 | 12 | <%_ } else { -%> 13 | 14 | <%_ } -%> 15 | <%= appname %> 16 | 17 | 18 | <%_ if (includeBootstrap) { -%> 19 | 20 | 21 | 22 | <%_ } -%> 23 | 24 | 25 | 26 | <%_ if (includeModernizr) { -%> 27 | 28 | 29 | 30 | <%_ } -%> 31 | 32 | 33 | 36 | <%_ if (includeBootstrap) { -%> 37 |
38 |
39 | 50 |

<%= appname %>

51 |
52 | 53 |
54 |

'Allo, 'Allo!

55 |

Always a pleasure scaffolding your apps.

56 |

Splendid!

57 |
58 | 59 |
60 |
61 |

HTML5 Boilerplate

62 |

HTML5 Boilerplate is a professional front-end template for building fast, robust, and adaptable web apps or sites.

63 | 64 | <%_ if (includeSass) { -%> 65 |

Sass

66 |

Sass is the most mature, stable, and powerful professional grade CSS extension language in the world.

67 | <%_ } -%> 68 | 69 |

Bootstrap

70 |

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

71 | 72 | <%_ if (includeModernizr) { -%> 73 |

Modernizr

74 |

Modernizr is an open-source JavaScript library that helps you build the next generation of HTML5 and CSS3-powered websites.

75 | <%_ } -%> 76 |
77 |
78 | 79 | 82 |
83 | <%_ } else { -%> 84 |
85 |

'Allo, 'Allo!

86 |

You now have

87 |
    88 |
  • HTML5 Boilerplate
  • 89 | <%_ if (includeSass) { -%> 90 |
  • Sass
  • 91 | <%_ } if (includeModernizr) { -%> 92 |
  • Modernizr
  • 93 | <%_ } if (includeJQuery) { -%> 94 |
  • jQuery
  • 95 | <%_ } -%> 96 |
97 |
98 | <%_ } -%> 99 | <%_ if (includeAnalytics) { -%> 100 | 101 | 109 | <%_ } -%> 110 | 111 | <%_ if (includeJQuery || includeBootstrap) { -%> 112 | 113 | <%_ } -%> 114 | <%_ if (includeBootstrap) { -%> 115 | 116 | <%_ } -%> 117 | 118 | 119 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /app/templates/main.css: -------------------------------------------------------------------------------- 1 | .browserupgrade { 2 | margin: 0.2em 0; 3 | background: #ccc; 4 | color: #000; 5 | padding: 0.2em 0; 6 | } 7 | 8 | <%_ if (includeBootstrap) { -%> 9 | /* Space out content a bit */ 10 | body { 11 | padding-top: 1.5rem; 12 | padding-bottom: 1.5rem; 13 | } 14 | 15 | /* Everything but the jumbotron gets side spacing for mobile first views */ 16 | .header, 17 | .marketing, 18 | .footer { 19 | padding-right: 1rem; 20 | padding-left: 1rem; 21 | } 22 | 23 | /* Custom page header */ 24 | .header { 25 | padding-bottom: 1rem; 26 | border-bottom: .05rem solid #e5e5e5; 27 | } 28 | 29 | /* Make the masthead heading the same height as the navigation */ 30 | .header h3 { 31 | margin-top: 0; 32 | margin-bottom: 0; 33 | line-height: 3rem; 34 | } 35 | 36 | /* Custom page footer */ 37 | .footer { 38 | padding-top: 1.5rem; 39 | color: #777; 40 | border-top: .05rem solid #e5e5e5; 41 | } 42 | 43 | /* Customize container */ 44 | @media (min-width: 48em) { 45 | .container { 46 | max-width: 46rem; 47 | } 48 | } 49 | .container-narrow > hr { 50 | margin: 2rem 0; 51 | } 52 | 53 | /* Main marketing message and sign up button */ 54 | .jumbotron { 55 | text-align: center; 56 | border-bottom: .05rem solid #e5e5e5; 57 | } 58 | .jumbotron .btn { 59 | padding: .75rem 1.5rem; 60 | font-size: 1.5rem; 61 | } 62 | 63 | /* Supporting marketing content */ 64 | .marketing { 65 | margin: 3rem 0; 66 | } 67 | .marketing p + h4 { 68 | margin-top: 1.5rem; 69 | } 70 | 71 | /* Responsive: Portrait tablets and up */ 72 | @media screen and (min-width: 48em) { 73 | /* Remove the padding we set earlier */ 74 | .header, 75 | .marketing, 76 | .footer { 77 | padding-right: 0; 78 | padding-left: 0; 79 | } 80 | /* Space out the masthead */ 81 | .header { 82 | margin-bottom: 2rem; 83 | } 84 | /* Remove the bottom border on the jumbotron for visual effect */ 85 | .jumbotron { 86 | border-bottom: 0; 87 | } 88 | } 89 | <%_ } else { -%> 90 | body { 91 | background: #fafafa; 92 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 93 | color: #333; 94 | } 95 | 96 | .hero-unit { 97 | margin: 50px auto 0 auto; 98 | width: 300px; 99 | font-size: 18px; 100 | font-weight: 200; 101 | line-height: 30px; 102 | background-color: #eee; 103 | border-radius: 6px; 104 | padding: 60px; 105 | } 106 | 107 | .hero-unit h1 { 108 | font-size: 60px; 109 | line-height: 1; 110 | letter-spacing: -1px; 111 | } 112 | <%_ } -%> 113 | -------------------------------------------------------------------------------- /app/templates/main.js: -------------------------------------------------------------------------------- 1 | console.log('\'Allo \'Allo!'); 2 | 3 | <%_ if (includeBootstrap) { -%> 4 | // Uncomment to enable Bootstrap tooltips 5 | // https://getbootstrap.com/docs/4.0/components/tooltips/#example-enable-tooltips-everywhere 6 | // $(function () { $('[data-toggle="tooltip"]').tooltip(); }); 7 | 8 | // Uncomment to enable Bootstrap popovers 9 | // https://getbootstrap.com/docs/4.0/components/popovers/#example-enable-popovers-everywhere 10 | // $(function () { $('[data-toggle="popover"]').popover(); }); 11 | <%_ } -%> 12 | -------------------------------------------------------------------------------- /app/templates/main.scss: -------------------------------------------------------------------------------- 1 | .browserupgrade { 2 | margin: 0.2em 0; 3 | background: #ccc; 4 | color: #000; 5 | padding: 0.2em 0; 6 | } 7 | 8 | <%_ if (includeBootstrap) { -%> 9 | /* Space out content a bit */ 10 | body { 11 | padding-top: 1.5rem; 12 | padding-bottom: 1.5rem; 13 | } 14 | 15 | /* Everything but the jumbotron gets side spacing for mobile first views */ 16 | .header, 17 | .marketing, 18 | .footer { 19 | padding-right: 1rem; 20 | padding-left: 1rem; 21 | } 22 | 23 | /* Custom page header */ 24 | .header { 25 | padding-bottom: 1rem; 26 | border-bottom: .05rem solid #e5e5e5; 27 | } 28 | 29 | /* Make the masthead heading the same height as the navigation */ 30 | .header h3 { 31 | margin-top: 0; 32 | margin-bottom: 0; 33 | line-height: 3rem; 34 | } 35 | 36 | /* Custom page footer */ 37 | .footer { 38 | padding-top: 1.5rem; 39 | color: #777; 40 | border-top: .05rem solid #e5e5e5; 41 | } 42 | 43 | /* Customize container */ 44 | @media (min-width: 48em) { 45 | .container { 46 | max-width: 46rem; 47 | } 48 | } 49 | .container-narrow > hr { 50 | margin: 2rem 0; 51 | } 52 | 53 | /* Main marketing message and sign up button */ 54 | .jumbotron { 55 | text-align: center; 56 | border-bottom: .05rem solid #e5e5e5; 57 | } 58 | .jumbotron .btn { 59 | padding: .75rem 1.5rem; 60 | font-size: 1.5rem; 61 | } 62 | 63 | /* Supporting marketing content */ 64 | .marketing { 65 | margin: 3rem 0; 66 | } 67 | .marketing p + h4 { 68 | margin-top: 1.5rem; 69 | } 70 | 71 | /* Responsive: Portrait tablets and up */ 72 | @media screen and (min-width: 48em) { 73 | /* Remove the padding we set earlier */ 74 | .header, 75 | .marketing, 76 | .footer { 77 | padding-right: 0; 78 | padding-left: 0; 79 | } 80 | /* Space out the masthead */ 81 | .header { 82 | margin-bottom: 2rem; 83 | } 84 | /* Remove the bottom border on the jumbotron for visual effect */ 85 | .jumbotron { 86 | border-bottom: 0; 87 | } 88 | } 89 | <%_ } else { -%> 90 | body { 91 | background: #fafafa; 92 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 93 | color: #333; 94 | } 95 | 96 | .hero-unit { 97 | margin: 50px auto 0 auto; 98 | width: 300px; 99 | font-size: 18px; 100 | font-weight: 200; 101 | line-height: 30px; 102 | background-color: #eee; 103 | border-radius: 6px; 104 | padding: 60px; 105 | h1 { 106 | font-size: 60px; 107 | line-height: 1; 108 | letter-spacing: -1px; 109 | } 110 | } 111 | <%_ } -%> 112 | -------------------------------------------------------------------------------- /app/templates/modernizr.json: -------------------------------------------------------------------------------- 1 | { 2 | "classPrefix": "modernizr-", 3 | "options": [ 4 | "addTest", 5 | "atRule", 6 | "domPrefixes", 7 | "hasEvent", 8 | "html5shiv", 9 | "html5printshiv", 10 | "load", 11 | "mq", 12 | "prefixed", 13 | "prefixes", 14 | "prefixedCSS", 15 | "setClasses", 16 | "testAllProps", 17 | "testProp", 18 | "testStyles" 19 | ], 20 | "feature-detects": [ 21 | "css/supports", 22 | "img/sizes", 23 | "img/srcset", 24 | "serviceworker", 25 | "touchevents" 26 | ] 27 | } -------------------------------------------------------------------------------- /app/templates/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org/ 2 | 3 | User-agent: * 4 | Disallow: 5 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | See the [contributing docs](https://github.com/yeoman/yeoman/blob/master/contributing.md) 2 | 3 | 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. 4 | 5 | If not, `generator-webapp` is fork-friendly and you can always maintain a custom version which you `npm install && npm link` to continue using via `yo webapp` or a name of your choosing. 6 | 7 | ## Support Questions 8 | 9 | We frequently get issues where people *modified* something after they finished scaffolding and things weren't working properly. These are classified as **support** questions; they are related to this generator, but not something we did wrong. It's best to ask those on [Stack Overflow] with tags `#yeoman` and `#gulp` instead of opening an issue here. You can also ask questions in our [gitter channel]. 10 | 11 | If the issue has already been opened before it turned out to be a support question, feel free to paste the link to the Stack Overflow question, so we can answer it. 12 | 13 | [stack overflow]: http://stackoverflow.com 14 | [gitter channel]: https://gitter.im/yeoman/yeoman 15 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | Welcome to the [gulp] flavor of our web app generator! If you're not familiar with gulp, we suggest checking out [their docs][gulp-docs]. 4 | 5 | If you haven't already, install [yo] and this generator by running: 6 | 7 | ```sh 8 | $ npm install --global yo generator-webapp 9 | ``` 10 | 11 | Now you can scaffold your very own web app: 12 | 13 | ```sh 14 | $ mkdir my-webapp 15 | $ cd my-webapp 16 | $ yo webapp 17 | ``` 18 | 19 | To start developing, run: 20 | 21 | ```sh 22 | $ npm run start 23 | ``` 24 | 25 | This will fire up a local web server, open http://localhost:9000 in your default browser and watch files for changes, reloading the browser automatically. 26 | 27 | To run the tests in the browser, run: 28 | 29 | ```sh 30 | $ npm run serve:test 31 | ``` 32 | 33 | To make a production-ready build of the app, run: 34 | 35 | ```sh 36 | $ npm run build 37 | ``` 38 | 39 | To preview the production-ready build to check if everything is ok: 40 | 41 | ```sh 42 | $ npm run serve:dist 43 | ``` 44 | 45 | ## Tasks 46 | 47 | To get the list of available tasks, run: 48 | 49 | ```sh 50 | $ npm run tasks 51 | ``` 52 | 53 | ## Gulp plugins 54 | 55 | Gulp plugins (the ones that begin with `gulp-`) don't have to be explicitly imported. They are automatically picked up by [gulp-load-plugins] and available through the `$` variable. 56 | 57 | ## Browser support 58 | 59 | You can configure browser support for Autoprefixer and @babel/preset-env by modifying the [browserslist] configuration, which in this case is the `browserslist` field in your `package.json`. 60 | 61 | ### Modernizr 62 | 63 | `modernizr.json` contains Modernizr configuration. You can use [this file][modernizr-config-all] as a reference for all available options. 64 | 65 | ## Linting 66 | 67 | We use ESLint for linting JavaScript code. You can define rules in your `package.json` under the `eslintConfig` field. Alternatively, you can add an `.eslintrc` file to your project root, where you can [configure][eslint-config] ESLint using JavaScript, JSON or YAML. 68 | 69 | ### The `no-undef` rule and tests 70 | 71 | The ESLint rule [`no-undef`][no-undef] will warn about usage of explicitly undeclared variables and functions. Because our tests use global functions like `describe` and `it` (defined by the testing framework), ESLint will consider those as warnings. 72 | 73 | Luckily, the fix is easy—add an `.eslintrc` file to the `test/spec` directory and let ESLint know about your testing framework. For example, if you're using Mocha, add this to `.eslintrc`: 74 | 75 | ```json 76 | { 77 | "env": { 78 | "mocha": true 79 | } 80 | } 81 | ``` 82 | 83 | Configuration from this `.eslintrc` will merge with your project-wide configuration. 84 | 85 | ## Serve 86 | 87 | We use the `.tmp` directory mostly for compiling assets like SCSS files. It has precedence over `app`, so if you had an `app/index.html` template compiling to `.tmp/index.html`, http://localhost:9000 would point to `.tmp/index.html`, which is what we want. 88 | 89 | This system can be a little confusing with the `watch` task, but it's actually pretty simple: 90 | 91 | * notify LiveReload when compiled assets change 92 | * run the compile task when source assets change 93 | 94 | E.g. if you have Less files, you would want to notify LiveReload when Less files have compiled, i.e. when `.tmp/styles/**/*.css` change, but you would want to compile Less files by running the `styles` task when source files change, i.e. `app/styles/**/*.less`. 95 | 96 | ### Adding New Assets 97 | 98 | #### Sass 99 | 100 | A common practice is to have a single, "main", Sass file, then use `@import` statements to add other partials. For example, let's say you created stylesheet for your navigation, `app/styles/_nav.scss`, you can then import it in `app/styles/main.scss` like this: 101 | 102 | ```scss 103 | @import "nav"; 104 | ``` 105 | 106 | #### JavaScript 107 | 108 | Our build step uses special `build` comment blocks to mark which assets to concatenate and compress for production. You can see them at the top and bottom of `app/index.html`. 109 | 110 | You have to add your own JS files manually. For example, let's say you created `app/scripts/nav.js`, defining some special behavior for the navigation. You should then include it in the comment blocks for your _source_ JS files, where `app/scripts/main.js` is located: 111 | 112 | ```html 113 | 114 | 115 | 116 | 117 | ``` 118 | 119 | Upon build these will be concatenated and compressed into a single file `scripts/main.js`. 120 | 121 | The file name in the comment block and the first source aren't related, their name being the same is a pure coincidence. The file name in the comment block specifies how the final optimized file will be called, while the sources should map to your source files. 122 | 123 | ## Debugging `gulpfile.js` 124 | 125 | Gulp tasks are not meant to be run directly, but instead through npm scripts. However, sometimes you want to run a tasks in order to debug it. If you don't have Gulp install globally, you can run the local CLI using `npx gulp`, so this is how you would run the `lint` task: 126 | 127 | ```sh 128 | $ npx gulp lint 129 | ``` 130 | 131 | Keep in mind that only exported tasks are available to the CLI: 132 | 133 | ```js 134 | function myPrivateTask() { 135 | // not available to CLI 136 | } 137 | 138 | function myPublicTask() { 139 | } 140 | 141 | // available to CLI as "myPublicTask" 142 | exports.myPublicTask = myPublicTask 143 | ``` 144 | 145 | [gulp]: https://github.com/gulpjs/gulp 146 | [gulp-docs]: https://gulpjs.com/docs/en/getting-started/quick-start 147 | [yo]: https://github.com/yeoman/yo 148 | [gulp-load-plugins]: https://github.com/jackfranklin/gulp-load-plugins 149 | [browserslist]: https://github.com/browserslist/browserslist 150 | [modernizr-config-all]: https://github.com/Modernizr/Modernizr/blob/master/lib/config-all.json 151 | [eslint-config]: https://eslint.org/docs/user-guide/configuring 152 | [no-undef]: https://eslint.org/docs/rules/no-undef 153 | -------------------------------------------------------------------------------- /docs/faq.md: -------------------------------------------------------------------------------- 1 | # Frequently Asked Questions 2 | 3 | ## Add templating engine X? 4 | 5 | There are a **lot** of templating languages: Pug, Swig, pure.js, Nunjucks, Underscore, Mustache etc. We don't believe there is a single right choice, it depends on the situation and desired complexity, so we chose not to have a built-in templating solution. However, we have [recipes](recipes) for implementing some of them, so you can use them as references. 6 | 7 | ## Organize paths into variables? 8 | 9 | While we understand that this is often useful as the scaffolded project grows, we believe it's a premature optimization in this case. If you're changing the project structure, most of the times a simple find & replace does the trick. Also, this feature is not very straightforward, it's not obvious what should be a variable and what shouldn't, so we think it's best to leave this one up to you. Read the whole discussion in [#295](https://github.com/yeoman/generator-webapp/issues/295). 10 | -------------------------------------------------------------------------------- /docs/recipes/README.md: -------------------------------------------------------------------------------- 1 | # Recipes 2 | 3 | - [CoffeeScript](coffeescript.md) 4 | - [Handlebars](handlebars.md) 5 | - [Less](less.md) 6 | - [Stylus](stylus.md) 7 | - [Pug](pug.md) 8 | - [GitHub Pages](gh-pages.md) 9 | - [Asset revisioning](asset-revisioning.md) 10 | - [React](react.md) 11 | - [React Router](react-router.md) 12 | - [Nunjucks](nunjucks.md) 13 | - [AWS S3 deployment](aws-s3-deployment.md) 14 | - [Heroku deployment - Node.js](node-heroku.md) 15 | - [Rsync deployment](rsync-deploy.md) 16 | - [Browserify](browserify.md) 17 | - [HTMLHint](htmlhint.md) 18 | - [Liquify](liquify.md) 19 | 20 | *We welcome additional recipes for common use-cases not covered by this generator.* 21 | 22 | ## Tips for writing a recipe 23 | 24 | ### 1. Use the `master` branch of the generator 25 | 26 | If you haven't already, clone the generator and link it: 27 | 28 | ```sh 29 | $ git clone https://github.com/yeoman/generator-webapp 30 | $ cd generator-webapp 31 | $ npm link 32 | $ cd ../ 33 | ``` 34 | 35 | Now the `yo webapp` command will use that version of the generator. To make sure this is actually true: 36 | 37 | ```sh 38 | $ npm ls -g generator-webapp 39 | # you should get something like 40 | /usr/local/lib 41 | └── generator-webapp@0.1.0 -> /Users/username/generator-webapp 42 | ``` 43 | 44 | To update the generator, all you need to do is: 45 | 46 | ```sh 47 | $ cd generator-webapp 48 | $ git pull origin master 49 | $ cd ../ 50 | ``` 51 | 52 | ### 2. Create a test project 53 | 54 | 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/): 55 | 56 | ```sh 57 | $ mkdir recipe-stylus && cd $_ 58 | $ yo webapp 59 | # select all options 60 | ``` 61 | 62 | ### 3. Track changes 63 | 64 | You should now create a Git repository and commit everything, this will allow you to see exactly which changes are required to implement Stylus: 65 | 66 | ```sh 67 | $ git init 68 | $ git add . 69 | $ git commit -m "Initial commit" 70 | ``` 71 | 72 | ### 4. Get your feature working 73 | 74 | Do whatever is necessary to get Stylus working, install required gulp plugins, change `gulpfile.js`, update styles etc. 75 | 76 | ### 5. Write the recipe 77 | 78 | After you've completed a minimal set of changes to implement Stylus, run `git diff` and use the output to write the recipe. 79 | -------------------------------------------------------------------------------- /docs/recipes/asset-revisioning.md: -------------------------------------------------------------------------------- 1 | # Asset revisioning 2 | 3 | This recipe demonstrates how to set up simple static asset revisioning (aka *revving*) for CSS and JS by appending content hash to their filenames `unicorn.css` → `unicorn-098f6bcd.css`. 4 | 5 | Make sure to set the files to [never expire](http://developer.yahoo.com/performance/rules.html#expires) for this to have an effect. 6 | 7 | ## Steps 8 | 9 | ### 1. Install dependencies 10 | 11 | Install these gulp plugins: 12 | 13 | ``` 14 | $ npm install --save-dev gulp-rev gulp-rev-rewrite 15 | ``` 16 | 17 | * [gulp-rev](https://github.com/sindresorhus/gulp-rev) appends content hashes 18 | * [gulp-rev-rewrite](https://github.com/TheDancingCode/gulp-rev-rewrite) updates references to those files 19 | 20 | ### 2. Update the `html` task 21 | 22 | Instead of wasting performance reading CSS and JS files into a new stream, we can notice that we already have that stream available in the `html` task, so we can just perform revving there: 23 | 24 | ```diff 25 | function html() { 26 | return src('app/*.html') 27 | .pipe($.useref({searchPath: ['.tmp', 'app', '.']})) 28 | .pipe($.if(/\.js$/, $.uglify({compress: {drop_console: true}}))) 29 | .pipe($.if(/\.css$/, $.postcss([cssnano({safe: true, autoprefixer: false})]))) 30 | + .pipe($.if(/\.js$/, $.rev())) 31 | + .pipe($.if(/\.css$/, $.rev())) 32 | + .pipe($.revRewrite()) 33 | .pipe($.if(/\.html$/, $.htmlmin({ 34 | collapseWhitespace: true, 35 | minifyCSS: true, 36 | minifyJS: {compress: {drop_console: true}}, 37 | processConditionalComments: true, 38 | removeComments: true, 39 | removeEmptyAttributes: true, 40 | removeScriptTypeAttributes: true, 41 | removeStyleLinkTypeAttributes: true 42 | }))) 43 | .pipe(dest('dist')); 44 | } 45 | ``` 46 | 47 | * `.pipe($.if('*.js', $.rev()))` – at this point we have JS files in the stream, so we are revving them 48 | * `.pipe($.if('*.css', $.rev()))` – at this point we have CSS files in the stream, so we are revving them 49 | * `.pipe($.revReplace())` – at this point we have CSS, JS and HTML files in the stream, so we are updating all references to revved files 50 | -------------------------------------------------------------------------------- /docs/recipes/aws-s3-deployment.md: -------------------------------------------------------------------------------- 1 | # AWS S3 Deployment 2 | 3 | This recipe demonstrates how to set up AWS S3 deployment task. 4 | 5 | 6 | ## Steps 7 | 8 | ### 1. Install dependencies 9 | 10 | Install the [gulp-awspublish](https://github.com/pgherveou/gulp-awspublish) gulp plugin: 11 | 12 | ``` 13 | $ npm install --save-dev gulp-awspublish 14 | ``` 15 | 16 | ### 2. Create a `deploy` task 17 | 18 | Add this task to your `gulpfile.js`. It will run `build` task before deploying: 19 | 20 | ```js 21 | gulp.task('deploy', ['default'], () => { 22 | // create a new publisher 23 | const publisher = $.awspublish.create({ 24 | params: { 25 | 'Bucket': '...' 26 | } 27 | }); 28 | 29 | // define custom headers 30 | const headers = { 31 | 'Cache-Control': 'max-age=315360000, no-transform, public' 32 | }; 33 | 34 | return gulp.src('dist/**/*.*') 35 | .pipe(publisher.publish(headers)) 36 | .pipe(publisher.sync()) 37 | .pipe(publisher.cache()) 38 | .pipe($.awspublish.reporter()); 39 | }); 40 | ``` 41 | 42 | ### 3. Add cache file to `.gitignore`. 43 | 44 | ``` 45 | .awspublish-* 46 | ``` 47 | 48 | `publisher.cache()` creates a through stream that creates or updates a cache file using file s3 path and file etag. Consecutive runs of publish will use this file to avoid reuploading identical files. 49 | 50 | ### 4. Deploy 51 | 52 | Run the following command to deploy: 53 | 54 | ``` 55 | $ gulp deploy 56 | ``` 57 | 58 | ### Tip 59 | 60 | It is highly recommended to enable [asset revisioning](asset-revisioning.md). 61 | -------------------------------------------------------------------------------- /docs/recipes/browserify.md: -------------------------------------------------------------------------------- 1 | # Setting up Browserify with Babelify 2 | 3 | With this setup you can import modules into your `main.js`. Only code used in `main.js` will be called by the app. 4 | 5 | 6 | ## Steps 7 | 8 | ### 1. Install the required plugins 9 | 10 | ```sh 11 | $ npm install --save-dev browserify babelify vinyl-buffer vinyl-source-stream 12 | ``` 13 | 14 | ### 2. Edit your `scripts` task 15 | 16 | ```diff 17 | const browserSync = require('browser-sync'); 18 | const del = require('del'); 19 | const wiredep = require('wiredep').stream; 20 | +const browserify = require('browserify'); 21 | +const babelify = require('babelify'); 22 | +const buffer = require('vinyl-buffer'); 23 | +const source = require('vinyl-source-stream'); 24 | ``` 25 | 26 | ```diff 27 | function scripts() { 28 | - return src('app/scripts/**/*.js') 29 | + const b = browserify({ 30 | + entries: 'app/scripts/main.js', 31 | + transform: babelify, 32 | + debug: true 33 | + }) 34 | + 35 | + return b.bundle() 36 | + .pipe(source('bundle.js')) 37 | .pipe($.plumber()) 38 | - .pipe($.if(!isProd, $.sourcemaps.init())) 39 | - .pipe($.babel()) 40 | + .pipe(buffer()) 41 | + .pipe($.if(!isProd, $.sourcemaps.init({loadMaps: true}))) 42 | .pipe($.if(!isProd, $.sourcemaps.write('.'))) 43 | .pipe(dest('.tmp/scripts')) 44 | .pipe(server.reload({stream: true})); 45 | }); 46 | ``` 47 | 48 | ### 3. Edit your `index.html` 49 | 50 | We are going to require the compiled bundle. 51 | 52 | ```diff 53 | 54 | 55 | 56 | - 57 | + 58 | 59 | 60 | 61 | ``` 62 | 63 | ### 4. Edit your `package.json` 64 | 65 | The linter needs to know about the module use. 66 | 67 | ```diff 68 | "eslintConfig": { 69 | "parserOptions": { 70 | + "ecmaVersion": 6, 71 | "sourceType": "module" 72 | }, 73 | ``` 74 | 75 | ## Usage 76 | 77 | - Put your modules in `app/scripts`, and require them in your `main.js`. 78 | 79 | For example, you could have `foo.js`: 80 | 81 | ```js 82 | export default function() { 83 | console.log('hello world'); 84 | } 85 | ``` 86 | 87 | And then in your `main.js`: 88 | 89 | ```js 90 | import foo from './foo'; 91 | foo(); // => hello world 92 | ``` 93 | -------------------------------------------------------------------------------- /docs/recipes/coffeescript.md: -------------------------------------------------------------------------------- 1 | # Setting up CoffeeScript 2 | 3 | With this setup you can freely mix `.js` and `.coffee` files in your `app/scripts` directory, and everything will just work. 4 | 5 | We strongly suggest to scaffold your app with Babel (the default behavior), that way it's easier to add CoffeeScript support using the existing infrastructure. 6 | 7 | 8 | ## Steps 9 | 10 | 11 | ### 1. Install dependencies the [gulp-coffee](https://github.com/wearefractal/gulp-coffee) plugin 12 | 13 | Current setup includes coding in JavaScript, compiling with Babel, and linting with ESLint. We're instead going to code in CoffeeScript and lint with [CoffeeLint](http://www.coffeelint.org/): 14 | 15 | ``` 16 | $ npm install --save-dev gulp-coffee 17 | ``` 18 | 19 | ### 2. Compile application code 20 | 21 | #### 2.1 Edit the `scripts` task 22 | 23 | The task previously only compiled `.js` files with Babel. We need to modify it to also compile `.coffee` files with CoffeeScript. This can be achieved very easily using gulp-if: 24 | 25 | ```diff 26 | gulp.task('scripts', () => { 27 | - return gulp.src('app/scripts/**/*.js') 28 | + return gulp.src('app/scripts/**/*.{js,coffee}') 29 | .pipe($.plumber()) 30 | .pipe($.sourcemaps.init()) 31 | - .pipe($.babel()) 32 | + .pipe($.if('.js', $.babel(), $.coffee())) 33 | .pipe($.sourcemaps.write('.')) 34 | .pipe(gulp.dest('.tmp/scripts')) 35 | .pipe(reload({stream: true})); 36 | }); 37 | ``` 38 | 39 | #### 2.2 Edit the `serve` task 40 | 41 | We need to tell gulp to recompile `.coffee` files on change, like we're already doing for `.js` files: 42 | 43 | ```diff 44 | gulp.task('serve', () => { 45 | runSequence(['clean', 'wiredep'], ['styles', 'scripts', 'fonts'], () => { 46 | ... 47 | 48 | gulp.watch('app/styles/**/*.scss', ['styles']); 49 | - gulp.watch('app/scripts/**/*.js', ['scripts']); 50 | + gulp.watch('app/scripts/**/*.{js,coffee}', ['scripts']); 51 | gulp.watch('app/fonts/**/*', ['fonts']); 52 | gulp.watch('bower.json', ['wiredep', 'fonts']); 53 | }); 54 | }); 55 | ``` 56 | 57 | ### 3. Compile tests 58 | 59 | #### 3.1 Create a `scripts:test` task 60 | 61 | This task compiles `.coffee` files located in `test/spec` into the `.tmp/spec` directory and reloads the browser. 62 | 63 | ```js 64 | gulp.task('scripts:test', () => { 65 | return gulp.src('test/spec/**/*.coffee') 66 | .pipe($.plumber()) 67 | .pipe($.coffee()) 68 | .pipe(gulp.dest('.tmp/spec')) 69 | .pipe(reload({stream: true})); 70 | }); 71 | ``` 72 | 73 | #### 3.2 Add `scripts:test` as a dependency of `serve:test` 74 | 75 | ```js 76 | gulp.task('serve:test', ['scripts', 'scripts:test'], () => { 77 | // ... 78 | }); 79 | ``` 80 | 81 | #### 3.3 Edit the `serve:test` task 82 | 83 | We should modify this task so that compiled `.js` files are served and that changes to `.coffee` files trigger recompilation of tests: 84 | 85 | ```diff 86 | gulp.task('serve:test', ['scripts', 'scripts:test'], () => { 87 | browserSync({ 88 | notify: false, 89 | port: 9000, 90 | ui: false, 91 | server: { 92 | - baseDir: ['test'], 93 | + baseDir: ['.tmp', 'test'], 94 | routes: { 95 | '/bower_components': 'bower_components' 96 | } 97 | } 98 | }); 99 | 100 | gulp.watch('app/scripts/**/*.js', ['scripts']); 101 | + gulp.watch('test/spec/**/*.coffee', ['scripts:test']); 102 | gulp.watch(['test/spec/**/*.js', 'test/index.html']).on('change', reload); 103 | gulp.watch('test/spec/**/*.js', ['lint:test']); 104 | }); 105 | ``` 106 | 107 | ### 4. Lint CoffeeScript files 108 | 109 | ESLint only supports JavaScript. In case you'd like to lint `.coffee` files too, we can set that up using CoffeeLint. 110 | 111 | #### 4.1 Install the [gulp-coffeelint](https://github.com/janraasch/gulp-coffeelint) plugin and [lazypipe](https://github.com/OverZealous/lazypipe) 112 | 113 | ``` 114 | $ npm install --save-dev gulp-coffeelint lazypipe 115 | ``` 116 | 117 | #### 4.2 Define lazypipe channels 118 | 119 | We want to lint `.js` files with ESLint and `.coffee` with CoffeeLint. This is not as trivial as compiling with Babel and CoffeeScript that we set up earlier because, based on the extension, we need to execute _multiple_ actions, not just one. 120 | 121 | For this task we will use lazypipe to set up _channels_ for ESLint and CoffeeLint: 122 | 123 | ```js 124 | const lazypipe = require('lazypipe'); 125 | 126 | // ... 127 | 128 | const eslintChannel = lazypipe() 129 | .pipe($.eslint, { fix: true }) 130 | .pipe(reload, {stream: true, once: true}) 131 | .pipe($.eslint.format) 132 | .pipe($.if, !browserSync.active, $.eslint.failAfterError()); 133 | 134 | const coffeelintChannel = lazypipe() 135 | .pipe($.coffeelint) 136 | .pipe($.coffeelint.reporter) 137 | .pipe($.if, !browserSync.active, $.coffeelint.reporter('fail')) 138 | ``` 139 | 140 | **Note**: We should not _call_ plugins when defining lazypipe channels! We should just pass the reference and arguments as needed, like this: 141 | 142 | ```js 143 | .pipe($.eslint, { fix: true }) 144 | ``` 145 | 146 | not this: 147 | 148 | ```js 149 | .pipe($.eslint({ fix: true })) 150 | ``` 151 | 152 | #### 4.3 Edit `lint` and `lint:test` tasks 153 | 154 | Now we should use those channels based on the file extension: 155 | 156 | ```diff 157 | function lint(files, options) { 158 | return gulp.src(files) 159 | - .pipe($.eslint({ fix: true })) 160 | - .pipe(reload({stream: true, once: true})) 161 | - .pipe($.eslint.format()) 162 | - .pipe($.if(!browserSync.active, $.eslint.failAfterError())); 163 | + .pipe($.if('*.js', eslintChannel(), coffeelintChannel())); 164 | } 165 | 166 | gulp.task('lint', () => { 167 | - return lint('app/scripts/**/*.js') 168 | + return lint('app/scripts/**/*.{js,coffee}') 169 | .pipe(gulp.dest('app/scripts')); 170 | }); 171 | gulp.task('lint:test', () => { 172 | - return lint('test/spec/**/*.js') 173 | + return lint('test/spec/**/*.{js,coffee}') 174 | .pipe(gulp.dest('test/spec')); 175 | }); 176 | ``` 177 | 178 | ## Usage 179 | 180 | - Put your `.coffee` files in `app/scripts` and `test/spec`, and include them in your HTML as if they're `.js` files (e.g. `app/scripts/foo.coffee` => ``). 181 | 182 | - It's fine to have a mixture of `.js` and `.coffee` files in your `app/scripts` directory, just make sure they don't have the same basename. If you have both `foo.js` and `foo.coffee`, one will overwrite the other when compiled. 183 | -------------------------------------------------------------------------------- /docs/recipes/gh-pages.md: -------------------------------------------------------------------------------- 1 | # Deploying to GitHub Pages 2 | 3 | In this recipe you will learn how to set up deployment to [GitHub Pages](https://pages.github.com). Your `gh-pages` branch will contain files from `dist`. 4 | 5 | 6 | ## Steps 7 | 8 | ### 1. Install [gulp-gh-pages](https://github.com/shinnn/gulp-gh-pages) 9 | 10 | We only need to install one dependency for this recipe: 11 | 12 | ``` 13 | $ npm install --save-dev gulp-gh-pages 14 | ``` 15 | 16 | ### 2. Create a `deploy` task 17 | 18 | We need to create a `deploy` task, which will deploy contents of `dist` to the remote `gh-pages` branch: 19 | 20 | ```js 21 | function ghPages() { 22 | return src('dist/**/*') 23 | .pipe($.ghPages()); 24 | } 25 | 26 | const deploy = series(build, ghPages); 27 | 28 | exports.deploy = deploy; 29 | ``` 30 | 31 | If you don't want to trigger a rebuild on each `gulp deploy`, feel free to remove the `build` part. 32 | 33 | Also, cache from this plugin will be saved to `.publish`, we can ignore it by adding this line to `.gitignore`: 34 | 35 | ``` 36 | .publish 37 | ``` 38 | 39 | ### 3. Ensure that your repository is on GitHub and that `origin` is set 40 | 41 | For gulp-gh-pages to work, we need to have our repository on GitHub, and the `origin` remote has to point there. We can check our remotes by running: 42 | 43 | ``` 44 | $ git remote -v 45 | ``` 46 | 47 | If `origin` doesn't exist, create it: 48 | 49 | ``` 50 | $ git remote add origin https://github.com/you/webapp.git 51 | ``` 52 | Not sure which URL to use? Check out "[Which remote URL should I use?](https://help.github.com/articles/which-remote-url-should-i-use/)" 53 | 54 | Our app will be hosted on the `gh-pages` branch, so we need to have it on the remote repository. We can create an [orphan branch](http://stackoverflow.com/a/4288660/1247274) by running: 55 | 56 | ``` 57 | $ git checkout --orphan gh-pages 58 | ``` 59 | 60 | In order to be able to push this branch, we have to have at least one commit, so let's create an empty one: 61 | 62 | ``` 63 | $ git commit -m "Initial commit" --allow-empty 64 | ``` 65 | 66 | Now we can push it to `origin`: 67 | 68 | ``` 69 | $ git push origin gh-pages 70 | ``` 71 | 72 | ## Usage 73 | 74 | 1. Run `gulp deploy`. 75 | 2. Visit `http://[your-username].github.io/[repo-name]`. 76 | 77 | It might take a couple of minutes for your page to show up the first time you push to `gh-pages`. 78 | 79 | ## Troubleshooting 80 | 81 | In case your `gulp deploy` command is failing, try deleting the cache by running: 82 | 83 | ``` 84 | $ rm -rf .publish 85 | ``` 86 | 87 | and trying again. 88 | -------------------------------------------------------------------------------- /docs/recipes/handlebars.md: -------------------------------------------------------------------------------- 1 | # Setting up Handlebars 2 | 3 | This recipe shows how to set up Handlebars to precompile your templates, including LiveReload integration. 4 | 5 | 6 | ## Steps 7 | 8 | ### 1. Install dependencies 9 | 10 | Install some gulp plugins: 11 | 12 | ``` 13 | $ npm install --save-dev gulp-handlebars gulp-define-module gulp-declare gulp-wrap gulp-concat 14 | ``` 15 | 16 | * [gulp-handlebars](https://github.com/lazd/gulp-handlebars) precompiles raw `.hbs` templates into JavaScript 17 | * [gulp-define-module](https://github.com/wbyoung/gulp-define-module) and [gulp-declare](https://github.com/lazd/gulp-declare) are used together to package up the compiled JavaScript template into a namespaced module 18 | 19 | Install Handlebars as a bower component: 20 | 21 | ``` 22 | $ bower install --save handlebars 23 | ``` 24 | 25 | * You need this so you can include the Handlebars runtime in your page – even compiled templates depend on this. (You won't need to include the entire Handlebars library though.) 26 | ``` 27 | // bower.json 28 | { 29 | "name": "MyApp", 30 | "dependencies": { 31 | ... 32 | }, 33 | "devDependencies": { 34 | ... 35 | }, 36 | "overrides": { 37 | "handlebars": { 38 | "main": ["handlebars.runtime.js"] 39 | } 40 | } 41 | } 42 | ``` 43 | * It's a good idea to verify you've installed the same version of Handlebars as the one used by internally by gulp-handlebars, to guarantee compatibility between the runtime and your compiled templates. Look in `node_modules/gulp-handlebars/package.json` under `"dependencies"` and check the handlebars version – if necessary, you can ask bower to install that specific version, e.g. `bower install --save handlebars#^1.3.0`. 44 | 45 | ### 2. Create a `templates` task 46 | 47 | ```js 48 | gulp.task('templates', () => { 49 | return gulp.src('app/templates/*.hbs') 50 | .pipe($.plumber()) 51 | .pipe($.handlebars()) 52 | .pipe($.defineModule('plain')) 53 | .pipe($.declare({ 54 | namespace: 'MyApp.templates' // change this to whatever you want 55 | })) 56 | .pipe($.concat('templates.js')) 57 | .pipe(gulp.dest('.tmp/templates')); 58 | }); 59 | ``` 60 | This compiles `.hbs` files into `templates.js` file in the `.tmp/templates` directory. 61 | 62 | ### 2.1 Using Partials 63 | 64 | ```js 65 | gulp.task('partials', function() { 66 | return gulp.src('app/templates/partials/*.hbs') 67 | .pipe($.plumber()) 68 | .pipe($.handlebars()) 69 | .pipe($.wrap('Handlebars.registerPartial(<%= processPartialName(file.relative) %>, Handlebars.template(<%= contents %>));', {}, { 70 | imports: { 71 | processPartialName: function(fileName) { 72 | // Escape the output with JSON.stringify 73 | return JSON.stringify(path.basename(fileName, '.js')); 74 | } 75 | } 76 | })) 77 | .pipe($.concat('partials.js')) 78 | .pipe(gulp.dest('.tmp/templates')); 79 | }); 80 | ``` 81 | This compiles the `.hbs` files inside the `templates/partials` directory into `partials.js` file in the `.tmp/templates` directory. 82 | 83 | ### 3. Add `templates` and `partials` as a dependencies of both `html` and `serve` 84 | 85 | ```js 86 | gulp.task('html', ['styles', 'templates', 'partials', 'scripts'], () => { 87 | ... 88 | ``` 89 | 90 | ```js 91 | gulp.task('serve', ['styles', 'templates', 'partials', 'scripts', 'fonts'], () => { 92 | ... 93 | ``` 94 | 95 | ### 4. Edit your `serve` task 96 | 97 | Edit your `serve` task so that (a) editing an `.hbs` file triggers the `templates` task, and (b) the browser is reloaded whenever a `.js` file is generated in `.tmp/templates`: 98 | 99 | ```diff 100 | gulp.task('serve', () => { 101 | - runSequence(['clean', 'wiredep'], ['styles', 'scripts', 'fonts'], () => { 102 | + runSequence(['clean', 'wiredep'], ['styles', 'scripts', 'templates', 'partials', 'fonts'], () => { 103 | ... 104 | gulp.watch([ 105 | 'app/*.html', 106 | 'app/images/**/*', 107 | '.tmp/fonts/**/*', 108 | + '.tmp/templates/**/*.js', 109 | ]).on('change', reload); 110 | 111 | gulp.watch('app/styles/**/*.scss', ['styles']); 112 | + gulp.watch('app/templates/*.hbs', ['templates']); 113 | + gulp.watch('app/templates/partials/*.hbs', ['partials']); 114 | gulp.watch('app/scripts/**/*.js', ['scripts']); 115 | gulp.watch('app/fonts/**/*', ['fonts']); 116 | gulp.watch('bower.json', ['wiredep', 'fonts']); 117 | }); 118 | }); 119 | ``` 120 | 121 | ## Usage 122 | 123 | Put your `.hbs` files in `app/templates` and `app/templates/partials` (if you using partials), and include in your HTML after the bower components: 124 | 125 | ```diff 126 | 127 | 128 | ... 129 | 130 | 131 | 132 | 133 | + 134 | + 135 | 136 | 137 | ``` 138 | 139 | You would then render the template like this: 140 | 141 | ```js 142 | const html = MyApp.templates.foo(); 143 | ``` 144 | 145 | The `MyApp.templates` namespace can be anything you want – change it in the `templates` task. 146 | -------------------------------------------------------------------------------- /docs/recipes/htmlhint.md: -------------------------------------------------------------------------------- 1 | # Linting HTML with HTMLHint 2 | 3 | In this very simple recipe you'll learn how to set up linting with [HTMLHint] using [gulp-htmlhint]. 4 | 5 | ## Steps 6 | 7 | ### 1. Install dependencies 8 | 9 | We need to install gulp-htmlhint as a dependency: 10 | 11 | ```sh 12 | $ npm install --save-dev gulp-htmlhint 13 | ``` 14 | 15 | ### 2. Create the task 16 | 17 | Let's create a task in our `gulpfile.js` which runs HTMLHint across all our HTML files and outputs the error in the terminal: 18 | 19 | ```js 20 | function htmlhint() { 21 | return src('app/**/*.html') 22 | .pipe($.htmlhint()) 23 | .pipe($.htmlhint.reporter()); 24 | } 25 | ``` 26 | 27 | Read [gulp-htmlhint's docs][api] to find out how you can pass options to HTMLHint. 28 | 29 | ### 3. Add the task as the dependency of `serve` and `html` 30 | 31 | HTMLHint should run on serve and build. 32 | 33 | ```diff 34 | const build = series( 35 | clean, 36 | parallel( 37 | lint, 38 | - series(parallel(styles, scripts), html), 39 | + series(parallel(htmlhint, styles, scripts), html), 40 | images, 41 | fonts, 42 | extras 43 | ), 44 | measureSize 45 | ); 46 | ``` 47 | 48 | ```diff 49 | let serve; 50 | if (isDev) { 51 | - serve = series(clean, parallel(styles, scripts, fonts), startAppServer); 52 | + serve = series(clean, parallel(htmlhint, styles, scripts, fonts), startAppServer); 53 | } else if (isTest) { 54 | serve = series(clean, scripts, startTestServer); 55 | } else if (isProd) { 56 | serve = series(build, startDistServer); 57 | } 58 | ``` 59 | 60 | ### 4. Run the task on each HTML change 61 | 62 | In the `serve` task add the following line to run `htmlhint` every time a HTML file in the `app` directory changes: 63 | 64 | ```diff 65 | watch([ 66 | 'app/*.html', 67 | 'app/images/**/*', 68 | '.tmp/fonts/**/*' 69 | ]).on('change', server.reload); 70 | 71 | + watch('app/*.html', htmlhint); 72 | watch('app/styles/**/*.css', styles); 73 | watch('app/scripts/**/*.js', scripts); 74 | watch('app/fonts/**/*', fonts); 75 | ``` 76 | 77 | ## Usage 78 | 79 | This is it! To test if everything is working correctly, try making a syntax error in your HTML file and saving it. You should see the error report in your terminal. 80 | 81 | [HTMLHint]: http://htmlhint.com/ 82 | [gulp-htmlhint]: https://github.com/bezoerb/gulp-htmlhint 83 | [api]: https://github.com/bezoerb/gulp-htmlhint#api 84 | -------------------------------------------------------------------------------- /docs/recipes/less.md: -------------------------------------------------------------------------------- 1 | # Setting up Less 2 | 3 | This recipe shows how to set up compiling styles with Less. 4 | 5 | ## Steps 6 | 7 | ### 1. *Choose Sass* when running the generator 8 | 9 | It sounds odd but this is the easiest way, because the task tree will be set up correctly for CSS preprocessing, and you can just switch out all the Sass or CSS references to Less ones. 10 | 11 | ### 2. Switch your npm dependencies 12 | 13 | Remove gulp-sass if you have selected it and install [gulp-less](https://github.com/plus3network/gulp-less) instead: 14 | 15 | ``` 16 | $ npm uninstall --save-dev gulp-sass && npm install --save-dev gulp-less 17 | ``` 18 | 19 | ### 3. Edit the `styles` and `startAppServer` tasks 20 | 21 | ```diff 22 | function styles() { 23 | - return src('app/styles/*.css') 24 | + return src('app/styles/*.less') 25 | .pipe($.plumber()) 26 | .pipe($.if(!isProd, $.sourcemaps.init())) 27 | - .pipe($.sass.sync({ 28 | - outputStyle: 'expanded', 29 | - precision: 10, 30 | - includePaths: ['.'] 31 | - }).on('error', $.sass.logError)) 32 | + .pipe($.less({ 33 | + paths: ['.'] 34 | + })) 35 | .pipe($.postcss([ 36 | autoprefixer() 37 | ])) 38 | .pipe($.if(!isProd, $.sourcemaps.write())) 39 | .pipe(dest('.tmp/styles')) 40 | .pipe(server.reload({stream: true})); 41 | }; 42 | ``` 43 | 44 | ```diff 45 | watch([ 46 | 'app/*.html', 47 | 'app/images/**/*', 48 | '.tmp/fonts/**/*' 49 | ]).on('change', server.reload); 50 | 51 | - watch('app/styles/**/*.css', styles); 52 | + watch('app/styles/**/*.less', styles); 53 | watch('app/scripts/**/*.js', scripts); 54 | watch('app/fonts/**/*', fonts); 55 | ... 56 | ``` 57 | 58 | 59 | ### 4. Check that it's working 60 | 61 | Delete `app/styles/main.scss` and replace it with your own `main.less`. 62 | 63 | Then verify that `npm run build` and `npm start` work correctly. While the server is running you should be able to edit your `main.less` and see the styles dynamically update in your browser. 64 | -------------------------------------------------------------------------------- /docs/recipes/liquify.md: -------------------------------------------------------------------------------- 1 | # Setting up Liquify 2 | 3 | With this setup you can use the [Liquid template language][liquid] (a template language which is used by [Shopify][shopify] and also [Jekyll][jekyll]) inside your HTML files to reduce duplications, make dynamic contents, and have fun using this cool template language! 4 | 5 | 6 | ## Steps 7 | 8 | ### 1. Install the required plugin 9 | 10 | ``` 11 | $ npm install --save-dev gulp-liquify 12 | ``` 13 | 14 | ### 2. Create a `liquify` task 15 | 16 | This task preprocesses your HTML files which used the Liquid template language and outputs the final files in `.tmp`. 17 | 18 | `liquify` method accepts your local object (an object which holds your local variables, the ones that can be accessed inside your Liquid templates) as the first argument; and a base directory for your templates to be included in an other template as the second argument. 19 | 20 | ```js 21 | gulp.task('liquify', () => { 22 | gulp.src('app/*.{html,liquid}') 23 | .pipe($.liquify({}, {base: 'app/_includes'})) 24 | .pipe(gulp.dest('.tmp')); 25 | }); 26 | ``` 27 | 28 | ### 3. Edit your `html` task 29 | 30 | Edit your `html` task so that (a) the `liquify` be as a dependency for it so that the generated HTML files be ready in `.tmp` before `html` task starts up, and (b) `html` task's gulp source will be the HTML files in `.tmp`, I mean the final generated files which are ready to be used by the `html` task. 31 | 32 | ```js 33 | gulp.task('html', ['liquify', 'styles', 'scripts'], () => { 34 | return gulp.src('.tmp/*.html') 35 | ... 36 | ``` 37 | 38 | ### 4. Edit your `serve` task 39 | 40 | Edit your `serve` task so that (a) the `liquify` be as a dependency for it so that the generated HTML files be ready in `.tmp` before `serve` task starts up, and (b) let gulp watch the HTML files in `.tmp` instead and also run the `liquify` task if any `.html` or `.liquid` file in `app` or `app/_includes` has been modified. 41 | 42 | ```js 43 | gulp.task('serve', () => { 44 | runSequence(['clean', 'wiredep'], ['liquify', 'styles', 'scripts', 'fonts'], () => { 45 | ... 46 | ``` 47 | 48 | ```diff 49 | gulp.task('serve', () => { 50 | ... 51 | 52 | gulp.watch([ 53 | - 'app/*.html', 54 | + '.tmp/*.html', 55 | 'app/images/**/*', 56 | '.tmp/fonts/**/*' 57 | ]).on('change', reload); 58 | 59 | + gulp.watch(['app/*.{html,liquid}', 'app/_includes/*.{html,liquid}'], ['liquify']); 60 | gulp.watch('app/styles/**/*.scss', ['styles']); 61 | gulp.watch('app/scripts/**/*.js', ['scripts']); 62 | gulp.watch('app/fonts/**/*', ['fonts']); 63 | gulp.watch('bower.json', ['wiredep', 'fonts']); 64 | }); 65 | }); 66 | ``` 67 | 68 | ## Usage 69 | 70 | - Put your Liquid templates in `app/_includes`, and include them in any of your `.html` or `.liquid` files. 71 | - Of course you can also easily use all of the other Liquid template language features such as tags, filters, operators, and etc... 72 | 73 | For example, you can have `head.liquid` template in `app/_includes`: 74 | 75 | ```html 76 | 77 | 78 | 79 | 80 | 81 | ``` 82 | 83 | And let your `index.html` in `app` include it: 84 | 85 | ```html 86 | 87 | 88 | 89 | {% assign style_name = "dark" %} 90 | {% include head.liquid %} 91 | 92 | Your Title 93 | 94 | 95 | ... 96 | 97 | 98 | ``` 99 | 100 | [liquid]: http://shopify.github.io/liquid/ 101 | [shopify]: https://www.shopify.com/ 102 | [jekyll]: https://jekyllrb.com/ 103 | -------------------------------------------------------------------------------- /docs/recipes/node-heroku.md: -------------------------------------------------------------------------------- 1 | # Deploying to Heroku using Node.js 2 | 3 | This is an easy way publish your site on Heroku using Node.js to serve the generated static files. 4 | 5 | ## Steps 6 | 7 | ### 1. Set dist/public as dist target 8 | 9 | In your gulpfile, change the distribution directory to dist/public, do not rename the taskname. [Example](https://gist.github.com/gaboesquivel/b71d153475141a8f1c61). Also update your `.gitignore` file. 10 | 11 | ### 2. Create dist/server.js 12 | 13 | ```js 14 | const express = require('express'); 15 | const serveStatic = require('serve-static'); 16 | const compression = require('compression'); 17 | const port = process.env.PORT || 3000; 18 | const domain = process.env.DOMAIN; 19 | 20 | function ensureDomain(req, res, next) { 21 | if (!domain || req.hostname === domain) { 22 | // OK, continue 23 | return next(); 24 | }; 25 | 26 | // handle port numbers if you need non defaults 27 | res.redirect(`http://${domain}${req.url}`); 28 | }; 29 | 30 | const app = express(); 31 | 32 | // at top of routing calls 33 | app.all('*', ensureDomain); 34 | 35 | app.use(compression()); 36 | 37 | // default to .html (you can omit the extension in the URL) 38 | app.use(serveStatic(`${__dirname}/public`, {'extensions': ['html']})); 39 | 40 | app.listen(port, () => { 41 | console.log('Server running...'); 42 | }); 43 | ``` 44 | 45 | ### 3. Create dist/package.json 46 | 47 | ```json 48 | { 49 | "main": "server.js", 50 | "scripts": { 51 | "start": "node server.js" 52 | }, 53 | "engines":{ 54 | "node": ">=4" 55 | }, 56 | "dependencies": { 57 | "compression": "^1.4.4", 58 | "express": "^4.12.3", 59 | "serve-static": "^1.9.2" 60 | } 61 | } 62 | ``` 63 | 64 | ### 4. Push the dist folder to Heroku 65 | 66 | Use the [Heroku Toolbelt](https://github.com/heroku/heroku) to create an app and push your `dist` folder to Heroku. 67 | 68 | That's it! 69 | -------------------------------------------------------------------------------- /docs/recipes/nunjucks.md: -------------------------------------------------------------------------------- 1 | # Setting up Nunjucks 2 | 3 | This recipe shows how to set up Nunjucks to compile your templates. 4 | 5 | We assume your directory structure will look something like this: 6 | 7 | ``` 8 | webapp 9 | └── app 10 | ├── about.njk 11 | ├── contact.njk 12 | ├── index.njk 13 | ├── includes 14 | │ ├── footer.njk 15 | │ └── header.njk 16 | └── layouts 17 | └── default.njk 18 | ``` 19 | 20 | If you had something different in mind, modify paths accordingly. 21 | 22 | ## Steps 23 | 24 | ### 1. Install dependencies 25 | 26 | Install [gulp-nunjucks-render](https://github.com/carlosl/gulp-nunjucks-render) to render Nunjucks template language to HTML: 27 | 28 | ``` 29 | $ npm install --save-dev gulp-nunjucks-render 30 | ``` 31 | 32 | ### 2. Convert `app/index.html` into a template `app/layouts/default.njk` 33 | 34 | In `app/index.html` replace `
` and its content (cut, so you can paste later) with the following: 35 | 36 | ```njk 37 | {% block content %}{% endblock %} 38 | ``` 39 | 40 | Now make it the default layout template: 41 | 42 | ``` 43 | $ mv app/index.html app/layouts/default.njk 44 | ``` 45 | 46 | ### 3. Create new Nunjucks `app/index.njk` page to extend from `app/layouts/default.njk` 47 | 48 | Create `app/index.njk`, where you can paste the `
` part from `app/index.html`: 49 | 50 | ```njk 51 | {% extends "layouts/default.njk" %} 52 | 53 | {% block content %} 54 |
55 | 56 |
57 | {% endblock %} 58 | ``` 59 | 60 | ### 4. Create a `views` task 61 | 62 | ```js 63 | function views() { 64 | return src('app/*.njk') 65 | .pipe($.nunjucksRender({ path: 'app' })) 66 | .pipe(dest('.tmp')) 67 | .pipe(server.reload({ stream: true })); 68 | }; 69 | ``` 70 | 71 | This compiles `app/*.njk` files into static `.html` files in the `.tmp` directory. 72 | 73 | This triggers Browsersync after `views` task is completed 74 | 75 | ### 5. Add `views` as a dependency of both `build` and `serve` 76 | 77 | ```diff 78 | if (isDev) { 79 | - serve = series(clean, parallel(styles, scripts, fonts), startAppServer); 80 | + serve = series(clean, parallel(views, styles, scripts, fonts), startAppServer); 81 | } else if (isTest) { 82 | - serve = series(clean, scripts, startTestServer); 83 | + serve = series(clean, parallel(views, scripts), startTestServer); 84 | } else if (isProd) { 85 | serve = series(build, startDistServer); 86 | } 87 | ``` 88 | 89 | ```diff 90 | const build = series( 91 | clean, 92 | parallel( 93 | lint, 94 | - series(parallel(styles, scripts), html), 95 | + series(parallel(views, styles, scripts), html), 96 | images, 97 | fonts, 98 | extras 99 | ), 100 | measureSize 101 | ); 102 | ``` 103 | 104 | ### 6. Configure `html` task to include files from `.tmp` 105 | 106 | ```diff 107 | function html() { 108 | - return src(['app/*.html']) 109 | + return src(['app/*.html', '.tmp/*.html']) 110 | .pipe($.useref({searchPath: ['.tmp', 'app', '.']})) 111 | ... 112 | ``` 113 | 114 | ### 7. Update `extras` 115 | 116 | We don't want to copy over `.njk` files in the build process: 117 | 118 | ```diff 119 | function extras() { 120 | return src([ 121 | 'app/*', 122 | - '!app/*.html' 123 | + '!app/*.html', 124 | + '!app/*.njk' 125 | ], { 126 | dot: true 127 | }).pipe(dest('dist')); 128 | }; 129 | ``` 130 | 131 | ### 8. Edit your `serve` task 132 | 133 | Edit your `serve` task so that editing `.html` and `.njk` files triggers the `views` task: 134 | 135 | ```diff 136 | watch([ 137 | 'app/*.html', 138 | 'app/images/**/*', 139 | '.tmp/fonts/**/*' 140 | ]).on('change', server.reload); 141 | 142 | + watch('app/**/*.{html,njk}', views); 143 | watch('app/styles/**/*.scss', styles); 144 | watch('app/scripts/**/*.js', scripts); 145 | watch('app/fonts/**/*', fonts); 146 | }); 147 | ``` 148 | 149 | Notice we are still watching `.html` files in `app` because our templates have a different extension. 150 | -------------------------------------------------------------------------------- /docs/recipes/pug.md: -------------------------------------------------------------------------------- 1 | # Setting up Pug (formerly known as Jade) 2 | 3 | This recipe demonstrates how to set up [Pug](https://pugjs.org) as your HTML template engine. In a similar way you can implement a different engine, like [Haml](http://haml.info/). 4 | 5 | We assume your directory structure will look something like this: 6 | 7 | ``` 8 | webapp 9 | └── app 10 | ├── about.pug 11 | ├── contact.pug 12 | ├── index.pug 13 | ├── includes 14 | │   ├── footer.pug 15 | │   └── header.pug 16 | └── layouts 17 | └── default.pug 18 | ``` 19 | 20 | If you had something different in mind, modify paths accordingly. 21 | 22 | ## Steps 23 | 24 | ### 1. Install dependencies 25 | 26 | Install the Pug gulp plugin: 27 | 28 | ``` 29 | $ npm install --save-dev gulp-pug 30 | ``` 31 | 32 | ### 2. Create a `views` task 33 | 34 | Add this task to your `gulpfile.js`, it will compile `.pug` files to `.html` files in `.tmp`: 35 | 36 | ```js 37 | function views() { 38 | return src('app/*.pug') 39 | .pipe($.plumber()) 40 | .pipe($.pug({pretty: true})) 41 | .pipe(dest('.tmp')) 42 | .pipe(server.reload({stream: true})); 43 | } 44 | ``` 45 | 46 | We are passing `pretty: true` as an option to get a nice HTML output, otherwise Pug would output the HTML on a single line, which would break our comment blocks for useref. 47 | 48 | ### 3. Add `views` task to `server` and `build` process 49 | 50 | ```js 51 | if (isDev) { 52 | serve = series(clean, parallel(views, styles, scripts, fonts), startAppServer); 53 | } else if (isTest) { 54 | serve = series(clean, parallel(views, scripts), startTestServer); 55 | } else if (isProd) { 56 | serve = series(build, startDistServer); 57 | } 58 | ... 59 | ``` 60 | 61 | ```js 62 | const build = series( 63 | clean, 64 | parallel( 65 | lint, 66 | series(parallel(views, styles, scripts), html), 67 | images, 68 | fonts, 69 | extras 70 | ), 71 | measureSize 72 | ); 73 | ... 74 | ``` 75 | 76 | ### 4. Update other tasks 77 | 78 | #### `html` 79 | 80 | We want to parse the compiled HTML: 81 | 82 | ```diff 83 | function html() { 84 | - return src(['app/*.html']) 85 | + return src(['app/*.html', '.tmp/*.html']) 86 | .pipe($.useref({searchPath: ['.tmp', 'app', '.']})) 87 | ... 88 | ``` 89 | 90 | #### `extras` 91 | 92 | We don't want to copy over `.pug` files in the build process: 93 | 94 | ```diff 95 | function extras() { 96 | return src([ 97 | 'app/*', 98 | - '!app/*.html', 99 | + '!app/*.html', 100 | + '!app/*.pug' 101 | ], { 102 | dot: true 103 | }).pipe(dest('dist')); 104 | }; 105 | ``` 106 | 107 | 108 | #### `serve` 109 | 110 | Recompile Pug templates on each change and reload the browser after an HTML file is compiled: 111 | 112 | ```diff 113 | watch([ 114 | 'app/*.html', 115 | 'app/images/**/*', 116 | '.tmp/fonts/**/*' 117 | ]).on('change', server.reload); 118 | 119 | + watch('app/**/*.pug', views); 120 | watch('app/styles/**/*.scss', styles); 121 | watch('app/scripts/**/*.js', scripts); 122 | watch('app/fonts/**/*', fonts); 123 | }); 124 | ``` 125 | 126 | ### 5. Rewrite `index.html` as `layout.pug` + `index.pug` 127 | 128 | To partially automatize this job, you can use [html2jade](https://github.com/donpark/html2jade). However, at the time of this writing html2jade has some drawbacks (e.g. doesn't support conditional comments) and the output requires cleanup. 129 | 130 | #### `app/layouts/default.pug` 131 | 132 | ```pug 133 | doctype html 134 | html.no-js(lang='') 135 | head 136 | meta(charset='utf-8') 137 | meta(name='description', content='') 138 | meta(name='viewport', content='width=device-width, initial-scale=1') 139 | title webapp 140 | link(rel='apple-touch-icon', href='apple-touch-icon.png') 141 | // Place favicon.ico in the root directory 142 | // build:css styles/vendor.css 143 | 144 | // endbuild 145 | // build:css styles/main.css 146 | link(rel='stylesheet', href='styles/main.css') 147 | // endbuild 148 | // build:js scripts/vendor/modernizr.js 149 | script(src='scripts/modernizr.js') 150 | // endbuild 151 | body 152 | 155 | .container 156 | .header 157 | ul.nav.nav-pills.float-right 158 | li.nav-item: a.nav-link.active(href='#') Home 159 | li.nav-item: a.nav-link(href='#') About 160 | li.nav-item: a.nav-link(href='#') Contact 161 | 162 | h3.text-muted webapp 163 | 164 | block content 165 | 166 | .footer 167 | p ♥ from the Yeoman team 168 | 169 | // Google Analytics: change UA-XXXXX-X to be your site's ID. 170 | script. 171 | (function(b,o,i,l,e,r){b.GoogleAnalyticsObject=l;b[l]||(b[l]= 172 | function(){(b[l].q=b[l].q||[]).push(arguments)});b[l].l=+new Date; 173 | e=o.createElement(i);r=o.getElementsByTagName(i)[0]; 174 | e.src='https://www.google-analytics.com/analytics.js'; 175 | r.parentNode.insertBefore(e,r)}(window,document,'script','ga')); 176 | ga('create','UA-XXXXX-X');ga('send','pageview'); 177 | 178 | // build:js scripts/vendor.js 179 | 180 | 181 | // endbuild 182 | // build:js scripts/main.js 183 | script(src='scripts/main.js') 184 | // endbuild 185 | ``` 186 | 187 | #### `app/index.pug` 188 | 189 | ```pug 190 | extends layouts/default 191 | 192 | block content 193 | .jumbotron 194 | h1.display-3 'Allo, 'Allo! 195 | p.lead Always a pleasure scaffolding your apps. 196 | p: a.btn.btn-lg.btn-success(href='#') Splendid! 197 | 198 | .row.marketing 199 | .col-lg-6 200 | h4 HTML5 Boilerplate 201 | p HTML5 Boilerplate is a professional front-end template for building fast, robust, and adaptable web apps or sites. 202 | 203 | h4 Sass 204 | p Sass is the most mature, stable, and powerful professional grade CSS extension language in the world. 205 | 206 |

Bootstrap

207 |

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

208 | 209 | h4 Modernizr 210 | p Modernizr is an open-source JavaScript library that helps you build the next generation of HTML5 and CSS3-powered websites. 211 | ``` 212 | 213 | ### 6. Test 214 | 215 | Check if everything is working properly. Run `gulp serve` and try changing a `.pug` file to see if the page updates etc. 216 | 217 | ### 7. Celebrate 218 | 219 | This wasn't exactly the simplest recipe ever; go grab a :beer: or something. 220 | -------------------------------------------------------------------------------- /docs/recipes/react-router.md: -------------------------------------------------------------------------------- 1 | # Setting up React Router in addition to React and JSX 2 | 3 | This recipe builds on the [React recipe](react.md) and gets you set up with React Router in addition to React and JSX. 4 | 5 | 6 | ## Steps 7 | 8 | ### 1. React recipe 9 | 10 | Follow the steps in the React recipe to get set up with React and JSX. 11 | 12 | ### 2. Add dependencies 13 | 14 | Install [React Router](https://github.com/rackt/react-router) and [webpack](https://github.com/webpack/webpack) (bundler for JavaScript modules): 15 | 16 | ``` 17 | $ npm install --save-dev react-router webpack 18 | ``` 19 | 20 | ### 3. Add a `webpack` script 21 | 22 | Add a file `webpack.js` in `app/scripts/` and add the following code to include React and React Router: 23 | 24 | ```js 25 | window.React = require('react'); 26 | window.Router = require('react-router'); 27 | ``` 28 | 29 | ### 3. Create a `webpack` task 30 | 31 | At the top of the gulpfile, add: 32 | 33 | ```js 34 | const webpack = require('webpack'); 35 | ``` 36 | 37 | Add the task. This task bundles the webpack script, including React Router, and outputs it in `.tmp/scripts`. 38 | 39 | ```js 40 | gulp.task('webpack', cb => { 41 | webpack({ 42 | entry: './app/scripts/webpack.js', 43 | output: { 44 | path: '.tmp/scripts/', 45 | filename: 'bundle.js', 46 | }, 47 | }, (err, stats) => { 48 | if (err) { 49 | throw new gutil.PluginError('webpack', err); 50 | } 51 | 52 | cb(); 53 | }); 54 | }); 55 | ``` 56 | 57 | ### 4. Add `webpack` as a dependency of `html` and `serve` 58 | 59 | ```js 60 | gulp.task('html', ['styles', 'templates', 'webpack'], () => { 61 | ... 62 | ``` 63 | 64 | ```js 65 | gulp.task('serve', ['styles', 'templates', 'webpack', 'fonts'], () => { 66 | ... 67 | ``` 68 | 69 | ### 5. Edit your `serve` task 70 | 71 | Edit your `serve` task so that editing the `webpack.js` file triggers the `webpack` task, and (b) the browser is refreshed whenever a `.js` file is generated in `.tmp/scripts`: 72 | 73 | ```diff 74 | gulp.task('serve', ['styles', 'templates', 'fonts'], () => { 75 | ... 76 | gulp.watch([ 77 | 'app/*.html', 78 | '.tmp/styles/**/*.css', 79 | 'app/scripts/**/*.js', 80 | '.tmp/scripts/**/*.js', 81 | 'app/images/**/*' 82 | ]).on('change', reload); 83 | 84 | gulp.watch('app/styles/**/*.scss', ['styles', reload]); 85 | gulp.watch('app/scripts/**/*.jsx', ['templates', reload]); 86 | + gulp.watch('app/scripts/**/webpack.js', ['webpack']); 87 | gulp.watch('bower.json', ['wiredep', 'fonts', reload]); 88 | }); 89 | ``` 90 | 91 | ### 6. Add the bundled script reference 92 | 93 | Insert a script tag into your `app/index.html`: 94 | 95 | ```diff 96 | 97 | + 98 | ``` 99 | -------------------------------------------------------------------------------- /docs/recipes/react.md: -------------------------------------------------------------------------------- 1 | # Setting up React and JSX 2 | 3 | This recipe gets you set up with React, including precompilation of JSX into JavaScript, integrated with LiveReload. 4 | 5 | ## Steps 6 | 7 | ### 1. Add dependencies 8 | 9 | Install [gulp-babel](https://github.com/babel/gulp-babel), and the required presets for transforming JSX templates into vanilla JavaScript: 10 | 11 | ``` 12 | $ npm install --save-dev gulp-babel babel-preset-es2015 babel-preset-react 13 | ``` 14 | 15 | Install [React](https://github.com/facebook/react) itself as a Bower component: 16 | 17 | ``` 18 | $ bower install --save react 19 | ``` 20 | 21 | Run the wiredep task to insert a script tag into your `app/index.html`: 22 | 23 | ``` 24 | $ gulp wiredep 25 | ``` 26 | 27 | ### 2. Create a `templates` task 28 | 29 | This task preprocesses `.jsx` files into pure JavaScript and outputs them in `.tmp/scripts`. 30 | 31 | ```js 32 | gulp.task('templates', () => { 33 | return gulp.src('app/scripts/**/*.jsx') 34 | .pipe($.sourcemaps.init()) 35 | .pipe($.babel({ 36 | presets: ['es2015','react'] 37 | })) 38 | .pipe($.sourcemaps.write()) 39 | .pipe(gulp.dest('.tmp/scripts')); 40 | }); 41 | ``` 42 | 43 | ### 3. Add `templates` as a dependency of `html` and `serve` 44 | 45 | ```js 46 | gulp.task('html', ['styles', 'templates'], () => { 47 | ... 48 | ``` 49 | 50 | ```js 51 | gulp.task('serve', ['styles', 'templates', 'fonts'], () => { 52 | ... 53 | ``` 54 | 55 | * The `serve` dependency means the generated `.js` files will be ready in `.tmp/scripts` before the server starts up 56 | * The `html` dependency means your JSX also gets compiled as part of the `gulp build` sequence – before the `html` task starts, so that the `.js` files are generated in time for [gulp-useref](https://github.com/jonkemp/gulp-useref) to concatenate them. 57 | 58 | ### 4. Edit your `serve` task 59 | 60 | Edit your `serve` task so that (a) editing a `.jsx` file triggers the `templates` task, and (b) the browser is refreshed whenever a `.js` file is generated in `.tmp/scripts`: 61 | 62 | ```diff 63 | gulp.task('serve', ['styles', 'templates', 'fonts'], () => { 64 | ... 65 | gulp.watch([ 66 | 'app/*.html', 67 | '.tmp/styles/**/*.css', 68 | 'app/scripts/**/*.js', 69 | + '.tmp/scripts/**/*.js', 70 | 'app/images/**/*' 71 | ]).on('change', reload); 72 | 73 | gulp.watch('app/styles/**/*.scss', ['styles', reload]); 74 | + gulp.watch('app/scripts/**/*.jsx', ['templates', reload]); 75 | gulp.watch('bower.json', ['wiredep', 'fonts', reload]); 76 | }); 77 | ``` 78 | 79 | 80 | ## Usage 81 | 82 | - Put your `.jsx` files anywhere in `app/scripts`, but include them in your HTML as if they're `.js` files – e.g. for `app/scripts/foo.jsx`, use ``. 83 | 84 | - It's fine to have a mixture of `.js` and `.jsx` files in your `app/scripts`. 85 | -------------------------------------------------------------------------------- /docs/recipes/rsync-deploy.md: -------------------------------------------------------------------------------- 1 | # Rsync Deploying 2 | 3 | This is an easy way publish your site with [rsync](https://rsync.samba.org/) to serve the generated static files. 4 | 5 | ## Steps 6 | 7 | ### 1. Install dependencies 8 | 9 | Install the [gulp-rsync](https://github.com/jerrysu/gulp-rsync) gulp plugin: 10 | 11 | ``` 12 | $ npm install --save-dev gulp-rsync 13 | ``` 14 | 15 | ### 2. Create a `rsync config` file 16 | 17 | Add a file to project root `rsync.json`. 18 | 19 | ```json 20 | { 21 | "hostname": "hostname or ip", 22 | "username": "username", 23 | "destination": "folder" 24 | } 25 | ``` 26 | 27 | For example: 28 | ```json 29 | { 30 | "hostname": "example.com", 31 | "username": "user1", 32 | "destination": "/home/httpd/website" 33 | } 34 | ``` 35 | 36 | ### 3. Create a `deploy` task 37 | 38 | Add this task to your `gulpfile.js`. It will run `build` task before deploying: 39 | 40 | ```js 41 | function rsync() { 42 | const rsyncConfig = require('./rsync.json'); 43 | 44 | return src('dist/**') 45 | .pipe($.rsync({ 46 | root: 'dist', 47 | hostname: rsyncConfig.hostname, 48 | username: rsyncConfig.username, 49 | destination: rsyncConfig.destination, 50 | progress: true 51 | })); 52 | }); 53 | 54 | const deploy = series(build, rsync); 55 | 56 | exports.deploy = deploy; 57 | ``` 58 | 59 | ### 4. Deploy 60 | 61 | Run the following command to deploy: 62 | 63 | ``` 64 | $ gulp deploy 65 | ``` 66 | 67 | ### Tips 68 | 69 | It is highly recommended to enable [asset revisioning](asset-revisioning.md). 70 | 71 | You can add `rsync.json` file to `.gitignore` to protect your connection data. 72 | ``` 73 | /rsync.json 74 | ``` 75 | -------------------------------------------------------------------------------- /docs/recipes/stylus.md: -------------------------------------------------------------------------------- 1 | # Setting up Stylus 2 | 3 | This recipe shows how to set up compiling your stylesheets using [Stylus](http://stylus-lang.com/). 4 | 5 | ## Steps 6 | 7 | ### 1. Choose Sass when scaffolding 8 | 9 | When scaffolding your app with `yo webapp`, choose Sass. This will make it easier for us to implement Stylus using the existing infrastructure. However, do not choose Bootstrap, we will add a Stylus port of Bootstrap later. 10 | 11 | ### 2. Convert `main.scss` into `main.styl` 12 | 13 | We should first convert `main.scss` to Stylus. One way to do this is to convert it from Sass to CSS, then from CSS to Stylus, so let's do that. To convert to `.css`, simply run our `styles` task: 14 | 15 | ``` 16 | $ gulp styles 17 | ``` 18 | 19 | Now you have `.tmp/styles/main.css`. To convert that to `.styl`, we need the [Stylus executable](http://stylus-lang.com/docs/executable.html): 20 | 21 | ``` 22 | $ npm install --global stylus 23 | $ stylus --css .tmp/styles/main.css app/styles/main.styl 24 | ``` 25 | 26 | Finally, add wiredep comments for injecting dependencies at the top: 27 | 28 | ```styl 29 | // bower:styl 30 | // endbower 31 | ``` 32 | 33 | Now that the conversion is complete, we can safely delete `app/styles/main.scss`. 34 | 35 | ### 3. Switch dependencies 36 | 37 | After setting up Stylus we'll no longer need Sass, so we can uninstall gulp-sass and install gulp-stylus: 38 | 39 | ``` 40 | $ npm uninstall --save-dev gulp-sass 41 | $ npm install --save-dev gulp-stylus 42 | ``` 43 | 44 | ### 4. Edit the `styles` task 45 | 46 | Unlike with Sass, Stylus doesn't ignore filenames starting with `_`, so we'll limit `gulp.src` only to `main.styl`: 47 | 48 | ```diff 49 | gulp.task('styles', () => { 50 | - return gulp.src('app/styles/*.scss') 51 | + return gulp.src('app/styles/main.styl') 52 | .pipe($.plumber()) 53 | .pipe($.sourcemaps.init()) 54 | - .pipe($.sass.sync({ 55 | - outputStyle: 'expanded', 56 | - precision: 10, 57 | - includePaths: ['.'] 58 | - }).on('error', $.sass.logError)) 59 | + .pipe($.stylus({ 60 | + paths: ['.'] 61 | + })) 62 | .pipe($.sourcemaps.init()) 63 | .pipe($.autoprefixer()) 64 | .pipe($.sourcemaps.write()) 65 | .pipe(gulp.dest('.tmp/styles')) 66 | .pipe(reload({stream: true})); 67 | }); 68 | ``` 69 | 70 | ### 5. Edit `serve` and `wiredep` tasks 71 | 72 | To watch `.styl` files and recompile on change, modify the following line in the `serve` task: 73 | 74 | ```diff 75 | gulp.task('serve', () => { 76 | runSequence(['clean', 'wiredep'], ['styles', 'scripts', 'fonts'], () => { 77 | ... 78 | 79 | - gulp.watch('app/styles/**/*.scss', ['styles']); 80 | + gulp.watch('app/styles/**/*.styl', ['styles']); 81 | gulp.watch('app/scripts/**/*.js', ['scripts']); 82 | gulp.watch('app/fonts/**/*', ['fonts']); 83 | gulp.watch('bower.json', ['wiredep', 'fonts']); 84 | }); 85 | }); 86 | ``` 87 | 88 | Next, we need to modify the `wiredep` task to inject dependencies into `main.styl`: 89 | 90 | ```diff 91 | gulp.task('wiredep', () => { 92 | - gulp.src('app/styles/*.scss') 93 | + gulp.src('app/styles/*.styl') 94 | ... 95 | }); 96 | ``` 97 | 98 | ### 6. Install regular Bootstrap 99 | 100 | There is a Styles port of Bootstrap, so let's install that one: 101 | 102 | ``` 103 | bower install --save bootstrap-stylus 104 | ``` 105 | 106 | However, some its [`main` field](https://github.com/maxmx/bootstrap-stylus/blob/master/bower.json#L4) is incomplete (JS files and fonts are missing), so we'll have to override it in our `bower.json`. We'll have to list every JS because the order matters (e.g. Popovers depend on Tooltips). Also, to ensure that jQuery is included before Bootstrap, we'll set it as a dependency: 107 | 108 | ```json 109 | { 110 | "name": "webapp", 111 | "private": true, 112 | "dependencies": { 113 | "jquery": "~2.1.1", 114 | "modernizr": "~2.8.1", 115 | "bootstrap-stylus": "^5.0.7" 116 | }, 117 | "overrides": { 118 | "bootstrap-stylus": { 119 | "main": [ 120 | "bootstrap/index.styl", 121 | "js/transition.js", 122 | "js/alert.js", 123 | "js/button.js", 124 | "js/carousel.js", 125 | "js/collapse.js", 126 | "js/dropdown.js", 127 | "js/modal.js", 128 | "js/tab.js", 129 | "js/affix.js", 130 | "js/scrollspy.js", 131 | "js/tooltip.js", 132 | "js/popover.js", 133 | "fonts/*" 134 | ], 135 | "dependencies": { 136 | "jquery": "1.9.1 - 3" 137 | } 138 | } 139 | } 140 | } 141 | ``` 142 | 143 | This weirdly specific version range for jQuery is taken from [Bootstrap's bower.json](https://github.com/twbs/bootstrap/blob/v3.3.7/bower.json#L32) and translates to `>=1.9.1 <=3` (read more about the [advanced range syntax](https://github.com/npm/node-semver#advanced-range-syntax)). 144 | 145 | [`bootstrap/index.styl`](https://github.com/maxmx/bootstrap-stylus/blob/master/bootstrap/index.styl) imports stylesheets from `bower_components/bootstrap-stylus`, so we should add that path to our Stylus configuration: 146 | 147 | ```js 148 | // ... 149 | .pipe($.stylus({ 150 | paths: ['.', './bower_components/bootstrap-stylus'] 151 | })) 152 | // ... 153 | ``` 154 | 155 | To check if everything is wired up correctly, run `gulp wiredep`. In `index.html` you should see something like: 156 | 157 | ```html 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | ``` 177 | 178 | and in `main.styl` you should see the following: 179 | 180 | ```styl 181 | // bower:styl 182 | @import "bower_components/bootstrap-stylus/bootstrap/index.styl" 183 | // endbower 184 | ``` 185 | 186 | ## Usage 187 | 188 | Add `.styl` files to `app/styles` and use `@import` or `@require` to include them. 189 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator-webapp", 3 | "version": "4.0.0-9", 4 | "description": "Scaffold out a front-end web app", 5 | "license": "BSD-2-Clause", 6 | "repository": "yeoman/generator-webapp", 7 | "author": "The Yeoman Team", 8 | "main": "app/index.js", 9 | "engines": { 10 | "node": ">=6" 11 | }, 12 | "scripts": { 13 | "lint": "eslint .", 14 | "test": "mocha --reporter spec --timeout 3000" 15 | }, 16 | "files": [ 17 | "app" 18 | ], 19 | "keywords": [ 20 | "yeoman-generator", 21 | "web", 22 | "app", 23 | "front-end", 24 | "h5bp", 25 | "modernizr", 26 | "jquery", 27 | "gulp" 28 | ], 29 | "dependencies": { 30 | "command-exists": "^1.2.8", 31 | "generator-jasmine": "^2.0.1", 32 | "generator-mocha": "^2.0.4", 33 | "mkdirp": "^0.5.1", 34 | "yeoman-generator": "^4.0.1", 35 | "yosay": "^2.0.2" 36 | }, 37 | "devDependencies": { 38 | "@babel/core": "^7.4.5", 39 | "eslint": "^5.16.0", 40 | "eslint-config-prettier": "^4.3.0", 41 | "eslint-plugin-prettier": "^3.1.0", 42 | "husky": "^2.3.0", 43 | "lint-staged": "^8.1.7", 44 | "mocha": "^6.1.4", 45 | "prettier": "^1.17.1", 46 | "yeoman-assert": "^3.1.1", 47 | "yeoman-test": "^2.0.0" 48 | }, 49 | "prettier": { 50 | "semi": true, 51 | "singleQuote": true, 52 | "trailingComma": "none", 53 | "endOfLine": "auto" 54 | }, 55 | "husky": { 56 | "hooks": { 57 | "pre-commit": "lint-staged", 58 | "pre-push": "yarn lint && yarn test", 59 | "pre-publish": "yarn lint && yarn test" 60 | } 61 | }, 62 | "lint-staged": { 63 | "*.js": [ 64 | "eslint --fix", 65 | "git add" 66 | ] 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeoman/generator-webapp/afac1ce6ccdbe3cd2f72b1a12700b79152c9c2c3/screenshot.png -------------------------------------------------------------------------------- /test/bootstrap.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const helpers = require('yeoman-test'); 3 | const assert = require('yeoman-assert'); 4 | 5 | describe('Bootstrap feature', () => { 6 | describe('on', () => { 7 | before(done => { 8 | helpers 9 | .run(path.join(__dirname, '../app')) 10 | .withPrompts({ features: ['includeBootstrap'] }) 11 | .on('end', done); 12 | }); 13 | 14 | it('should add jQuery and Popper.js as dependencies', () => { 15 | assert.fileContent('package.json', '"jquery": "'); 16 | assert.fileContent('package.json', '"popper.js": "'); 17 | }); 18 | 19 | it('should enable jQuery environment in ESLint', () => { 20 | assert.fileContent('package.json', '"jquery": true'); 21 | }); 22 | }); 23 | 24 | describe('off', () => { 25 | before(done => { 26 | helpers 27 | .run(path.join(__dirname, '../app')) 28 | .withPrompts({ 29 | features: [], 30 | includeJQuery: false 31 | }) 32 | .on('end', done); 33 | }); 34 | 35 | it('should not contain jQuery or Popper.js', () => { 36 | assert.noFileContent('package.json', '"jquery": "'); 37 | assert.noFileContent('package.json', '"popper.js": "'); 38 | }); 39 | }); 40 | 41 | // Bootstrap 4 42 | describe('with Sass', () => { 43 | before(done => { 44 | helpers 45 | .run(path.join(__dirname, '../app')) 46 | .withPrompts({ 47 | features: ['includeSass', 'includeBootstrap'] 48 | }) 49 | .on('end', done); 50 | }); 51 | 52 | it('should use Bootstrap', () => { 53 | assert.fileContent('package.json', '"bootstrap"'); 54 | }); 55 | 56 | it('should output the correct