Gulp workflow: minimalizacja zdjęć i konwersja na webp

Kiedyś w zamierzchłych czasach używałem perla + lib ImageMagick do konwersji zdjęć, ale mamy XXI wiek i Node 😉
Od jakiegoś czasu zmienił się mój workflow dotyczący obróbki zdjęć na potrzeby stron internetowych.
Do tej pory używałem gulp-image, gulp-imagemin i wiele innych, ale zawsze coś było nie tak, zawsze czegoś brakowało.
Ostatnio używam takiego zapisu jak poniżej

<picture>
    <source class="img-responsive" srcset="./images/thumbnail/IMG_5600.webp" type="image/webp">
    <source class="img-responsive" srcset="./images/thumbnail/IMG_5600.jpg" type="image/jpeg">
    <img class="img-responsive" src="./images/thumbnail/IMG_5600.jpg" alt="">
</picture>

i potrzebowałem również webp, no i co następna biblioteka gulp-webp jest potrzebna 🙁
Mały research i okazało się że jest biblioteka sharp, która bardzo dobrze zastępuje ImageMagick więcej na tej stronie. Biblioteka ta jest w stanie zastąpić wszystko co do tej poru używałem.

Obecnie korzystam z pluginu gulp-responsive który pod spodem używa tejże biblioteki sharp.
Zmiana rozmiaru obrazu za pomocą tej biblioteki jest zazwyczaj 4x-5x szybsza niż w przypadku najszybszych ustawień ImageMagick i GraphicsMagick, a więc czemu jej nie używać. Oczywiście ta bibliotek nie tylko do zmniejsza grafiki ale również przycina, zmieniania formatu, zapisu do grayscale czyli szarości i wiele innych użytecznych rzeczy potrafi 🙂

Dobra zaczynamy cały proces.
Oczywiście musimy mieć zainstalowane node w naszym środowisku ale to nie temat na ten artykuł.

Na początek tworzymy package.json

npm init -y

Instalujemy wszystkie potrzebne biblioteki

npm i -D gulp gulp-responsive gulp-load-plugins

Tworzymy folder ze zdjęciami „sources” następnie gulpfile.js

var gulp = require('gulp');
var $ = require('gulp-load-plugins')();

gulp.task('images', function () {
    return gulp.src(['sources/**/*.{jpg,png}'])
        .pipe($.responsive({
            '*.jpg': [{
                width: 1200,
                height: 800
            }, {
                // Konwertujemy jpg do formatu webp
                width: 1200,
                height: 800,
                rename: {
                    extname: '.webp'
                }
            }]
        }, {
                // globalne ustawienia
                max: true,
                quality: 65,
                progressive: true,
                withMetadata: false,
                errorOnEnlargement: true
            }))
        .pipe(gulp.dest('dist'));
});

Oczywiście aby ten wynalazek uruchomić należy tak jak wszystkie taski w gulpie czyli:

gulp images

Ustawienia globalne:
max parametr potrzebny gdy zdjęcie jest zrobione w pionie – trzyma proporcje
quality wiadomo jakość dla grafik jpg jak i webp
progressive, jpg ładuje się od góry do dołu linia po linii, progresywny ładuje się całościowo w „kiepskiej” jakości i jest stopniowo wyostrzany
withMetadata usuwane są wszystkie metadane z pliku
errorOnEnlargement pokazuje błędy jeżeli takowe się pojawią

Biblioteka jest bardzo szybka 137 zdjęć (ponad 550MB) w rozdzielczości 2700×1800 zajęło tej bibliotece 32s z ustawieniami globalnymi jak powyżej bez webp. Oczywiście szybkość zależy od hardwaru jaki posiadamy.

Więcej można przeczytać na githubie opisane są tam bardziej skomplikowane użycia tej biblioteki jak dostępne opcje konfiguracyjne.

Gulp ES6 z Babel

Do gulp instalujemy:

npm install babel-core babel-preset-es2015 --save-dev

W głównym folderze tworzymy plik:

.babelrc

W nim dodajemy zapis:

{
  "presets": [
    "es2015"
  ]
}

Aby zadziałał nam baabel z gulp należy zmodyfikować nazwę gulpfile.js na gulpfile.babel.js
Ja na wszelki wypadek duplikuję plik i zmieniam nazwę tak na wszelki wypadek i później dokonuję odpowiednich zmian w gulpfile.babel.js

'use strict';

import gulp from 'gulp';
import sass from 'gulp-sass';
import autoprefixer from 'gulp-autoprefixer';
import sourcemaps from 'gulp-sourcemaps';

const dir = {
  src: 'src',
  dest: 'build'
};

const sassPaths = {
  src: `${dir.src}/style.scss`,
  dest: `${dir.dest}/style/`
};

gulp.task('style', () => {
  return gulp.src(sassPaths.src)
    .pipe(sourcemaps.init())
    .pipe(sass.sync().on('error', plugins.sass.logError))
    .pipe(autoprefixer())
    .pipe(gulp.dest(sassPaths.dest));
});

...

Rozwijane menu

Najpierw budujemy strukturę html. Wszystko jest robione w scss, js w wersji ES6 i gulp
Do ES6 używam gulp-babel aby przekonwertować js do es2015

<div class="container">

    <div id="accorions">
        <div class="accordion">Section 1</div>
        <div class="panel">
            <p>Sauerkraut can be garnished with cored avocado, also try decorateing the cake with hollandaise sauce.
                Asparagus can be marinateed with crushed lettuce, also try flavoring the loaf with salsa verde.
                with lobsters drink cream.</p>
        </div>

        <div class="accordion">Section 2</div>
        <div class="panel">
            <p>Aww, raid me parrot, ye dead landlubber! Swashbuckling, coal-black whales cowardly vandalize a mighty, gutless scabbard.
                Scabbards sing on booty at port degas! Small, dead tobaccos darkly fight a black, lively dagger.
                seashells fall with love.</p>
        </div>

        <div class="accordion">Section 3</div>
        <div class="panel">
            <p>Turbulence at the bridge was the adventure of mineral, lowered to a gravimetric mermaid.
                Moon at the universe was the mystery of coordinates, feeded to a real planet.
                wisely handle a processor.</p>
        </div>
    </div>

</div>

Dodajemy trochę css, na początku importuję normalize

@import "normalize";

.container {
    max-width: 500px;
    margin: 200px auto;

}

.accordion {
    background-color: #eee;
    color: #444;
    cursor: pointer;
    padding: 18px;
    width: 100%;
    border: none;
    text-align: left;
    outline: none;
    font-size: 15px;
    transition: 0.4s;
    &:after {
        content: '\002B';
        color: #777;
        font-weight: bold;
        float: left;
        margin-right: 5px;
    }
    &:hover {
        background-color: #ddd;
    }
    &.active {
        background-color: #ddd;
        &:after {
            content: "\2212";
        }
    }
}

.panel {
    padding: 0 18px;
    background-color: white;
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.2s ease-out;
    li {
        list-style: none;
    }
}

Teraz najważniejsza część, dodajemy js

"use strict";

class Accordion {

    constructor(options) {
        this.options = options;
    }

    creatAccordion() {
        let acc = this.getAccordionName();

        [...acc].forEach((el, i) => {
            el.addEventListener('click', el => {
                if(this.options.type === true && !this.hasClass(el, this.options.activeName)) {                
                    this.removeActiveAndPanelHeight();
                }
                el.target.classList.toggle(this.options.activeName);
                let panel = el.target.nextElementSibling;
                if (panel.style.maxHeight) {
                    panel.style.maxHeight = null;
                } else {
                    panel.style.maxHeight = panel.scrollHeight + 'px';
                }
            })
        })

    }

    getAccordionName() {
        return document.getElementsByClassName(this.options.accordionName);
    }

    hasClass(element, cla) {
        return (' ' + element.className + ' ').indexOf(' ' + cla + ' ') > -1;
    }

    removeActiveAndPanelHeight() {
        const currentActive = document.querySelectorAll("." + this.options.activeName);
        const heightPanel = document.querySelectorAll("." + this.options.panelName);

        [...currentActive].forEach(ca => {
            ca.classList.remove(this.options.activeName);
        });

        [...heightPanel].forEach(hp => {
            hp.removeAttribute("style");
        });
    }
}

const options = {
    'accordionName': 'accordion',
    'activeName': 'active',
    'panelName': 'panel',
    'type': true
};

const accordion = new Accordion(options);
accordion.creatAccordion();

Przykład można zobaczyć pod tym adresem

Ostatni parametr w options czyli ‚type’: true lub false umożliwia sterowaniem zwijania menu, true powoduje że każde kliknięcie na menu zwija inne rozwinięte zaś false pozostawia rozwinięte każde kliknięcie.
Oczywiście options można przenieść do html aby z tego miejsca sterować nie musi być ten obiekt umieszczony w js.

Całość włącznie z gulpem można zobaczyć na github

Gulp.js – menadżer zadań

Gulp.js jest menadżerem zadań opartym na node, w skrócie mamy kilka czynności do zrobienia w projekcie. I żeby nie robić tego ręcznie gulp wykonuje za nas te operacje.
Oprę się na małym projekcie, chcę less zamienić na css, skopiować do foldera css, zminimalizować i zmienić jego nazwę na style.min.css

1. Instalacja

Do tego oczywiście potrzebny najpierw jest zainstalowany node, jak już jest zainstalowany to instalujemy gulp.

// najpierw instalujemy globalnie – zainstaluje się w folderze C:\Users\Uzytkownik\AppData\Roaming\npm\node_modules\gulp

npm install -g gulp

// następnie instalujemy lokalnie w moim wypadku wchodzimy do D:\xampp\htdocs\test-gulp i tam instalujemy gulp jak i pluginy

npm install gulp --save-dev

Teraz aby móc z gulp skorzystać należy utworzyć plik konfiguracyjny gulpfile.js – musi być on umieszczony w katalogu głównym naszego projektu.

W naszym projekcie będziemy korzystać z gulp-less, gulp-minify-css oraz gulp-rename możemy je zainstalować pojedynczo jak i za jednym razem jak kto woli.

// wszystko instalujemy lokalnie

npm install gulp-less gulp-minify-css gulp-rename install --save-dev

Struktura naszego projektu:
mamy folder „test-gulp” w nim foldery css oraz less, w less jest plik style.less 🙂
W test-gulp znajduje się plik gulpfile.js (nasze taski) oraz package.json (plik generujemy automatycznie lub ręcznie 😉 – zawiera on dependencies itd.)

test-gulp/
|  css/
|  less/
|  |  style.less
gulpfile.js -- ad3
package.json -- ad2

2. Tworzymy package.json

Aby utworzyć package.json należy w konsoli lokalnie uruchomić npm init

Pojawi się nam coś takiego:

———————–
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sane defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install –save` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
name: (test-gulp)
———————–

Naciskamy enter do pojawienia się „Is this ok? (yes)
wpisujemy y + enter

i mamy w naszym projekcie package.json

3. Konfiguracja gulpfile.js

/**
 * umieszczamy nasze pakiety które nam są potrzebne do projektu
 */

var gulp        = require('gulp'),
    less        = require('gulp-less'),  // less -> css
    minifyCSS   = require('gulp-minify-css'),  // css -> minify css
    rename      = require('gulp-rename');  // zmiana style.css na style.min.css


/**
 * tworzymy task (zadanie) wszystkie pliki less
 * zamieniamy na css
 * z css usuwamy br, nowe linie itd. czyli poczciwe minify
 * style.css zmieniamy nazwę na style.min.css
 */

gulp.task('less', function() {
    return gulp.src('less/*.less')
        .pipe(less())
        .pipe(minifyCSS())
        .pipe(rename({suffix: '.min'}))
        .pipe(gulp.dest('css')); // miejsce docelowe pliku css
});

/**
 * Watch Files For Changes - czyli obserwator zmian? (dziwnie to brzmi po polsku :))
 */

gulp.task('watch', function() {
    gulp.watch('less/*.less', ['less']);
});

/**
 * Default Task - domyślne zadania?
 */

gulp.task('default', ['less', 'watch']);

5. Uruchamianie

Aby zmiany w plikach były na bieżąco kompilowane należy uruchomić będąc w naszym folderze (test-gulp) w konsoli wpisując gulp.
Od tego momentu każda zmian na less będzie skutkowała wygenerowanie pliku style.min.css w folderze css.

Oczywiście każdy task może być uruchamiany osobno, wystarczy w konsoli także lokalnie wpisać gulp less

6. Podsumowanie

Gulp może robić z naszych plików zip, kopiować na serwer itd. 🙂
Więcej na stronie gulp plugins

W następnym artykule pokaże jak można to samo skonfigurować w Intelij IDEA.