Yeoman

Start the modern Web development!

Matthieu Lux

Disclaimer

Trolls inside

Past


yeoman init      # Initialize and scaffold a new project using generator templates
yeoman build     # Build an optimized version of your app, ready to deploy
yeoman server    # Launch a preview server which will begin watching for changes
yeoman test      # Run a Mocha test harness in a headless PhantomJS

yeoman install   # Install a package from the clientside package registry
yeoman uninstall # Uninstall the package
yeoman update    # Update a package to the latest version
yeoman list      # List the packages currently installed
yeoman search    # Query the registry for matching package names
yeoman lookup    # Look up info on a particular package
					

Present

Yo

Grunt

Bower

Futur

"Opinions are my own"

Generate code?

  • It's wrong! ... Yes but?
  • The build, a special case
  • Build processes are more and more complex
  • There are more and more tools to integrate
  • There is no 2 projects with the same exact needs

Generate... then modify!

The Yeoman "Workflow"

Warm-up


$ npm install -g yo bower grunt-cli gulp
$ npm install -g generator-webapp
$ yo
					

Initiate the project

Dev with livereload

Preconfigured tests

Optimized build process

Create a generator

Create an NPM project


{
  "name": "generator-name",
  "version": "0.1.0",
  "description": "",
  "files": ["app", "router"],
  "keywords": ["yeoman-generator"],
  "dependencies": {
    "yeoman-generator": "^0.17.3"
  }
}
					

Extends the Generator "class"


var generators = require('yeoman-generator');

module.exports = generators.Base.extend({
  // The name `constructor` is important here
  constructor: function () {
    // Calling the super constructor is important
    // so our generator is correctly set up
    generators.Base.apply(this, arguments);

    // Next, add your custom code
    // This method adds support for a `--coffee` flag
    this.option('coffee');
  }
});
					
https://github.com/yeoman/generator

Inquirer


module.exports = generators.Base.extend({
  prompting: function () {
    var done = this.async();
    this.prompt({
      type    : 'input',
      name    : 'name',
      message : 'Your project name',
      default : this.appname // Default to current folder name
    }, function (answers) {
      this.log(answers.name);
      done();
    }.bind(this));
  }
})
					
https://github.com/sboudrias/inquirer.js

Gruntfile editor


module.exports = generators.Base.extend({
  writing: function () {
    this.gruntfile.insertConfig('compass', '{ foo: "bar" }');
    // output:
    // grunt.initConfig({
    //   compass: {
    //     watch: { watch: true }
    //   }
    // });

    this.gruntfile.registerTask('build', 'compass');
    // output:
    // grunt.registerTask('build', ['compass']);
  }
});
					
https://github.com/sboudrias/gruntfile-editor

File management


generators.Base.extend({
  writing: function () {
    this.fs.copyTpl(
      this.templatePath('index.html'),
      this.destinationPath('public/index.html'),
      { title: 'Templating with Yeoman' }
    );
  }
});
					
https://github.com/sboudrias/mem-fs-editor

Yosay

Essential!

https://github.com/yeoman/yosay

The generators

http://yeoman.io/generators/

The purpose

Start a new project with any technology

Backend

Node.js & Express

Spring Boot

Node.js & Express


server.get('/frameworks/:_id', function (req, res) {
  MongooseModel.findOne(req.params, function (err, doc) {
    if(err) {
      res.status(500).send('Error', err);
    }
    res.send(doc);
  });
});
					

Spring Boot


@RestController
@RequestMapping("/api")
public class UserResource {
    @Inject
    private UserRepository userRepository;

    @RequestMapping(value = "/users/{login}",
            method = RequestMethod.GET,
            produces = MediaType.APPLICATION_JSON_VALUE)
    ResponseEntity<User> getUser(@PathVariable String login) {
        return userRepository.findOneByLogin(login)
                .map(user -> new ResponseEntity<>(user, HttpStatus.OK))
                .orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));
    }
}
					

Framework UI

Bootstrap

Material Design

Bootstrap


Hello, world!

This is a template.

Learn more

Heading

Donec id elit non mi porta.

View details

Material Design


<body layout="column">
  
    
    

Hello World

</body>

Framework Web

Angular

React

Angular


@Component({selector: 'my-app'})
@Template({inline: '

Hi {{ name }}

'}) // Component controller class MyAppComponent { constructor() { this.name = 'Swiip'; } }

React


var CommentBox = React.createClass({
  render: function() {
    return (
      
Hello, world! I am a CommentBox.
); } }); React.render( <CommentBox />, document.getElementById('content') );

Build

Grunt

Gulp

Grunt


module.exports = function(grunt) {
  grunt.initConfig({
    jshint: {
      files: ['Gruntfile.js', 'src/**/*.js', 'test/**/*.js']
    },
    watch: {
      files: ['<%= jshint.files %>'],
      tasks: ['jshint']
    }
  });
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.registerTask('default', ['jshint']);
};
					

Gulp


var gulp = require('gulp');
var coffee = require('gulp-coffee');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');
var sourcemaps = require('gulp-sourcemaps');

gulp.task('scripts', function() {
  return gulp.src([
    'client/js/**/*.coffee',
    '!client/external/**/*.coffee'
  ]).pipe(sourcemaps.init())
      .pipe(coffee())
      .pipe(uglify())
      .pipe(concat('all.min.js'))
    .pipe(sourcemaps.write())
    .pipe(gulp.dest('build/js'));
});
					

Gestion de dépendances

Bower

NPM

Common JS

Browserify

Webpack

CommonJS


//example.js

exports.hello = function() {
  return 'Hello World'
};
					

//index.js

var example = require('./example.js');

example.hello();
					

Préprocesseur JS

Babel

CoffeeScript

Babel


import React from 'react';

export default class HelloWorld extends React.Component {
  constructor() {
    this.state = { hello: 'world' };
  }

  render() {
    return (
      

Hello { this.state.hello }!

); } });

CoffeeScript


class Animal
  constructor: (@name) ->

class Parrot extends Animal

Animal::rip = true

parrot = new Parrot("Macaw")
alert("This parrot is no more") if parrot.rip
					

JS Linter

JS Hint

ES Lint

CSS Preprocessor

Sass

Less

Sass


#header {
  height: 72px;
  background: $header-background-color;
  @include clearfix;

  h1 {
    color: white;
    @include float-left;
    a {
      @include replace-text("logo.jpg");
      width: 116px;
      height: 72px;
      display: block;
      text-decoration: none;
    }
  }
}
					

Less


#box {
  a {
    background-color: @primaryColor;
    color: #fff;
    display: block;

    height: @boxHeight;
    width: @boxWidth;
    line-height: @boxHeight;

    text-align: center;
    text-decoration: none;

    .roundedCorners(20px);
    .boxShadow;
  }
}
					

HTML Preprocessor

Jade

Haml

Jade


doctype html
html(lang="en")
  head
    title= pageTitle
    script(type='text/javascript').
      if (foo)
         bar(1 + 5)
  body
    h1 Jade - node template engine
    #container.col
      if youAreUsingJade
        p You are amazing
      else
        p Get on it!
      p.
        Jade is a terse and simple templating language with a
        strong focus on performance and powerful features.
					

Haml


#content
  .left.column
    %h2 Welcome to our site!
    %p= print_information
  .right.column
    = render :partial => "sidebar"
					

Development server

connect-livereload

Browser Sync

Browser Sync

Test frameworks

Jasmine

Mocha

Tests


describe("A spec using beforeEach and afterEach", function() {
  var foo = 0;

  beforeEach(function() {
    foo += 1;
  });
  afterEach(function() {
    foo = 0;
  });

  it("is just a function, so it can contain any code", function() {
    expect(foo).toEqual(1);
  });
  it("can have more than one expectation", function() {
    expect(foo).toEqual(1);
  });
});
					

Test tools

Karma

Istanbul

Karma


[18:07:20] Starting 'test'...
INFO [karma]: Karma v0.12.31 server started at http://localhost:9876/
INFO [launcher]: Starting browser PhantomJS
WARN [watcher]: Pattern "/Users/Swiip/Workspace/Sandbox/gulp-angular/src/**/*.mock.js" does not match any file.
INFO [PhantomJS 1.9.8 (Mac OS X)]: Connected on socket kupuXHYFbdMl74Vi5xS2 with id 47216180
PhantomJS 1.9.8 (Mac OS X): Executed 1 of 1 SUCCESS (0.004 secs / 0.016 secs)
[18:07:23] Finished 'test' after 2.87 s
					

Istanbul


=============================================================================
Writing coverage object [/Users/Swiip/Workspace/GitHub/generator-gulp-angular/coverage/coverage.json]
Writing coverage reports at [/Users/Swiip/Workspace/GitHub/generator-gulp-angular/coverage]
=============================================================================

=============================== Coverage summary ===============================
Statements   : 100% ( 667/667 )
Branches     : 100% ( 308/308 )
Functions    : 100% ( 94/94 )
Lines        : 100% ( 667/667 )
================================================================================
					

generator-gulp-angular

  • Best practices project (folder structures)
  • Angular specific build tools (ngAnnotate, angularFileSort)
  • Optimized development workflow with BrowserSync
  • Out of the box tests: Karma & Protractor
  • State of the art build optimization
  • LOTS of tools & preprocessors integrated

Tools

Preprocessors

THE END

Thanks!

@Swiip