Composer, Bower a Grunt

Správa závislostí a automatizácia úloh vo vašom projekte

By Milan Herda / @moriquend 11/2014

Kto som

Milan Herda, na internete perún alebo moriquend

Composer

Composer

Composer

David Grudl:
Composer, nejdůležitější nástroj pro PHP vývojáře

Inštalácia 3rd party knižníc kedysi

  • ručne
  • pear
  • pecl

Dôsledky pre váš kód

  • čo projekt, to systém
  • snaha väčšinu vecí poriešiť in-house
  • monolitizácia kódu

Dôsledky pre PHP ekosystém

  • každý framework rieši knižnice po svojom
  • malá prenositeľnosť kódu medzi frameworkami/projektami
  • spolu s pomalým vývojom a neduhmi PHP nastáva odliv vývojárov k iným jazykom

Záchrana

  • Vznik PHP-FIG (Framework Interop Group)
  • PSR-0 (autoload)
  • composer

Čo nám composer priniesol

  • Veľmi jednoduchý a rýchly spôsob inštalácie PHP knižníc do projektu.
  • Jednotný spôsob ukladania knižníc (do vendor adresára)
  • Autoloading pre nainštalované knižnice
  • Riešenie závislostí
  • Knižnice už nemusia byť fyzickou súčasťou projektu a predsa ich má každý vývojár k dispozícii

Čo priniesol pre PHP komunitu

  • Nastal boom vývoja a zdieľania knižníc a komponentov.
  • Frameworky začali svoju codebase členiť na jednotlivé samostatne použiteľné moduly.
  • Vývojári viac dbajú na package design = vyššia kvalita kódu
  • Zachránil svet :)

Trochu praxe

Inštalácia


                    $ curl -sS https://getcomposer.org/installer | php
                    

                    $ alias composer='composer.phar'
                    

Potrebujem knižnicu na parsovanie YAML súborov


                    $ composer require symfony/yaml
                    

Čo ak knižnica závisí na iných knižniciach?


                    $ composer require doctrine/orm
                    

composer.json

Špecifikácia vyžadovaných knižníc. Verzie nemusia byť zapísané presne, stačí približne a composer si automaticky dotiahne najnovšiu vyhovujúcu.


{
    "require": {
        "symfony/yaml": "~2.5",
        "doctrine/orm": "~2.4"
    }
}
                    

composer.lock

Do tohto súboru si composer poznačí presné verzie (tj. major.minor.patch alebo git hash musí sedieť) nainštalovaných knižníc a ktokoľvek kedykoľvek napíše v adresári s projektom príkaz:


                    $ composer install
                    

Composer mu automaticky nainštaluje všetky chýbajúce knižnice podľa definície v composer.lock.

composer install/update

Dva základné príkazy pre composer

.

                    $ composer install
                    

Pokiaľ existuje súbor composer.lock, tak nainštaluje knižnice podľa špecifikácie v tomto súbore.

Pokiaľ neexistuje, inštaluje podľa compose.json


                    $ composer update
                    

Inštaluje podľa composer.json a nainštalované verzie zapisuje do composer.lock

Kde sú nainštalované knižnice

Composer automaticky inštaluje do adresáru s názvom vendor a po každej inštalácii vytvorí aj súbory pre autoloading.

Odkiaľ composer berie názvy balíčkov?

  • packagist.org
  • Dajú sa nakonfigurovať vlastné (aj privátne) zdroje

Example time!

Vytvoríme si knižnicu na generovanie náhodnej farby a ukážeme si, ako ju nainštalovať do nášho projektu.

Spustíme príkazy


$ mkdir -p random-color
$ cd random-color
$ composer init
                    

Výsledný composer.json


{
    "name": "perun/random-color",
    "description": "Stupid library to generate random color code",
    "authors": [
        {
            "name": "Milan Herda",
            "email": "perun@perunhq.org"
        }
    ],
    "minimum-stability": "dev",
    "autoload": {
        "psr-0": {
            "": "src"
        }
    },
    "require": {}
}
                    

Adresárová štruktúra

PSR-0 vyžaduje, aby sme mali zdrojové súbory v adresároch podľa vzoru vendor-name/package-name a je zaužívané, že táto štruktúra je ešte uložená v adresári src. V našom prípade teda vytvoríme príslušný adresár:


$ mkdir -p src/Perun/RandomColor
                    

Pokiaľ chcete jednoduchšiu štruktúru, použite PSR-4. PSR-0 je už aj tak deprecated.

PHP trieda


<?php

namespace Perun\RandomColor;

/**
 * Generates random color
 *
 * @author Milan Herda <perun@perunhq.org>
 */
class RandomColorGenerator
{
    /**
     * @return string
     */
    public function generate()
    {
        return 'rgb(' . rand(0, 255) . ', ' . rand(0, 255) . ', ' . rand(0, 255) . ')';
    }
}

                    

Ako balík nainštalovať?

Aby bol balíček inštalovateľný cez composer, tak sa k nemu composer musí dostať.

Možnosti:

  • Zverejniť na packagist
  • PEAR/PECL
  • "Zverejniť" na privátnom/firemnom zozname balíčkov (postavenom napr. na satis)
  • Ponúknuť linku na VCS repozitár (ideálne GIT)

Git repozitár


$ git init
$ git add .
$ git ci -am "initial commit"
$ git tag v0.0.1
                    

Máme knižnicu, poďme ju použiť

Niekde inde na disku si vytvoríme adresár s naším novým projektom a v ňom vytvoríme composer.json s nasledovným obsahom:


{
    "name": "perun/project",
    "authors": [
        {
            "name": "Milan Herda",
            "email": "perun@perunhq.org"
        }
    ],
    "repositories": [
        {
            "type": "vcs",
            "url": "/home/perun/lib/random-color/"
        }
    ],
    "require": {
        "perun/random-color": "~0.0.1"
    }
}
                    

V projekte vytvoríme index.php


<?php

require 'vendor/autoload.php';

use Perun\RandomColor\RandomColorGenerator;

$randomColorGenerator = new RandomColorGenerator();

echo $randomColorGenerator->generate();
                    

Profit!

Next level

Skutočnú silu uvidíte až vo chvíli, keď si balíčky budete umiestňovať do nejakého repozitára dostupného všetkým členom tímu:

  • Github (privátne repozitáre sú platené)
  • Bitbucket (privátne repozitáre do 10 ľudí sú zadarmo)
  • Gitlab (zadarmo, inštalujete si u seba)

Zhrnutie

Package manager pre PHP

Inštaluje balíčky a poskytuje načítavanie tried cez autoloading

Bower

Bower je package manager pre frontendový JavaScript*

Už nikdy nebudete sťahovať jquery, bootstrap, angular ručne.

* a css

Inštalácia

Bower sa inštaluje pomocou NPM

NPM je package manager pre server-side JavaScript

Inštalácia


                    $ npm install -g bower
                    

Chcem bootstrap a lodash

bower.json


{
  "name": "perun/project",
  "version": "0.0.1",
  "authors": [
    "Milan Herda <perun@perunhq.org>"
  ],
  "license": "proprietary",
  "private": true,
  "dependencies": {
    "lodash": "~2.4.1",
    "bootstrap": "~3.2.0"
  }
}
                    

Ale do adresára assets/vendor

.bowerrc


{
    "directory": "assets/vendor"
}
                    

Stiahnutie balíkov


                    $ bower install
                    

Stiahne balíčky definované v bower.json do adresára (defaultne bower_components)

Update verzie stiahnutých balíkov


                    $ bower update
                    

bower nemá žiaden lock súbor ako má composer. Pri install a update priamo porovnáva to, čo už nainštaloval. Pokiaľ nejaký balíček chýba, tak ho dotiahne.

Zodpovednosti bower-u

bower iba sťahuje balíčky a rieši ich vzájomné závislosti pri inštalácii.

Neposkytuje žiadnu funcionalitu pre "includovanie". Tj. stiahnutý balíček si musíte do webu vložiť ručne cez <script> tag alebo pomocou nejakého loaderu (napr. requirejs)

Grunt

Grunt je task runner

  • minifikovanie js a css
  • preklad less/sass/coffee
  • zmenšovanie objemu statických obrázkov
  • sledovanie zmien na súboroch počas vývoja a automatický refresh browseru
  • kontrola štábnej kultúry (jshint, jslint)
  • spúšťanie unit testov
  • čokoľvek iné

Inštalácia


                    $ npm install -g grunt-cli
                    

Príklad

Gruntfile.js


module.exports = function(grunt) {

    // Project configuration.
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        srcAssetsDir: 'assets',
        webDir: 'www',
        clean: ['www/assets'],
        copy: {
            main: {
                files: [
                    {
                        expand: true,
                        src: ['assets/**'],
                        dest: 'www/'
                    },
                    {
                        expand: true,
                        cwd: 'assets/vendor/bootstrap/fonts',
                        src: ['*'],
                        dest: 'www/assets/css/fonts/'
                    }
                ]
            }
        },
        sass: {
            dist: {
                files: [{
                    expand: true,
                    cwd: 'assets/sass',
                    src: ['*.scss'],
                    dest: 'assets/css',
                    ext: '.css'
                }]
            }
        },
        watch: {
            all: {
                files: [
                    'assets/**/*',
                    'www/*html',
                ],
                tasks: ['clean', 'sass', 'copy', 'jshint', 'jslint'],
                options: {
                    livereload: true,
                    spawn: false
                }
            }
        },
        jshint: {
            options: {
                reporter: require('jshint-stylish'),
                bitwise: true,
                camelcase: true,
                curly: true,
                eqeqeq: true,
                forin: true,
                immed: true,
                indent: 4,
                latedef: true,
                newcap: true,
                noarg: true,
                //nonbsp: true,
                nonew: true,
                plusplus: true,
                quotmark: 'single',
                undef: true,
                unused: true,
                strict: true,
                trailing: true,
                globals: {
                    define: true,
                    require: true,
                    window: true,
                    document: true
                }
            },
            all: [
                'assets/js/**/*.js'
            ]
        },
        jslint: {
            main: {
                src: 'assets/js/**/*.js',
                directives: {
                    predef: ['define', 'require', 'window', 'document']
                },
                options: {
                    failOnError: false,
                }
            }
        },
    });

    grunt.loadNpmTasks('grunt-contrib-clean');
    grunt.loadNpmTasks('grunt-contrib-copy');
    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-contrib-sass');
    grunt.loadNpmTasks('grunt-contrib-jshint');
    grunt.loadNpmTasks('grunt-jslint');

    // Default task(s).
    grunt.registerTask('default', ['clean', 'copy']);

};
                    

Spustenie tasku


                    $ grunt watch
                    

Gulp

Gulp je task runner

Gulp a Grunt sú konkurenti a oba robia to isté. Rozdiel je v spôsobe.

Gruntove tasky sa zapisujú pomocou konfigurácie. Pokiaľ máte task zložený so subtaskov, tak každý task sa vykonáva samostatne a teda po každom sa zmeny v súboroch zapíšu na disk.

Gulpove tasky sa zapisujú direktívne. Namiesto konfigurácie píšete javascriptové príkazy. Zložené tasky vedia zmeny posielať cez pipe-u, tj. na disk sa zapíše až na konci.

Gulp je z definície rýchlejší a flexibilnejší.

Grunt existuje dlhšie a má viac pluginov.

Aktuálne je len vecou vkusu, čo si kto vyberie. Časom ale zrejme prevládne Gulp. Pokiaľ nepríde tretí hráč.