/ Learning Log

Learning Log - Week #2

Minął tydzień - czas na kolejny "odcinek" learning log'a :) Dzisiaj o funkcjach zmieniających stan komponentu poza komponentem, setState który nie powoduje przerenderowania, wepack v4 - mode + zero config, czy nodejs-8 jest szybszy niż nodejs-6 (?) oraz npx - czyli jak odpalać binarki z node_modules/.bin. Zapraszam.

  1. Zmiana stanu poza komponentem
  2. setState bez re-renderowania
  3. Webpack v4 - mode + zero config is amazing!
  4. Speed of node v8 vs v6
  5. npx

Zmiana stanu poza komponentem

O tym, że setState jako parametr może przyjąć funkcję - wiedziałem :) setState jest asynchroniczny, więc przekazując funkcję, możemy bezpiecznie zmienić stan bazując na wartości ze stanu poprzedniego:

toggleState() {
   this.setState((state, props) => ({ visible: !state.visible });
}

Ale o tym, że funkcja zmieniająca stan może być zadeklarowana poza componentem, nie pomyślałem! :)

import { Component } from "react";

const increment = (state, props) => ({ value: state.value + props.step });

class Cmp extends Component {
    
    handleIncrement = () => {
        this.setState(increment);
    }
    
    render() {
        return(
            <div>
                {state.value}
                <button onClick={ this.handleIncrement }>+</button>
           </div>
        );
    }
}

Jedną z zalet tego podejścia jest możliwość testowania w izolacji funkcji, która odpowiedzialna jest za ustawienie nowego stanu. Można też kolejkować wywołania setState dla skoplikowanych przejść między stanami komponentu. Zakolejkowane tranzycje zostaną wywołane z zachowaniem kolejności wywołania setState.

setState bez re-renderowania

Szybki TIP - co zrobić, żeby setState nie powodował prze-renderowania komponentu?

Zwrócić null :)

Ten feature jest dostępny od wersji v16 React'a.

import React, { Component } from "react";

class MyComponent extends Component {

    handleChange = (event) => {
        const value = event.target.value;
        this.setState((state) => 
            value !== state.value ? { value } : null
        );
    }
    
    render() {
        return (
            <input onChange={ this.handleChange } />
        );
    }
}

W tym wypadku jeśli wartość input'u jest taka sama jak wartość zapisana w stanie - nasz komponent nie zostanie prze-renderowany.

Webpack v4 - mode & zero config

Jeśli nie wiecie to aktualnie pracuję w Egnyte nad utrzymaniem i rozbudowywaniem aplikacji typu SPA dla jednego z naszych firmowych produktów - Egnyte-Protect. Aplikację bundlujemy za pomocą webpacka. Jeśli chodzi o rozmiar docelowego bundla to wychodzi on w granicach 2,5MB. Sporo. Ale nie o tym dziś chciałem mówić. Sam proces budowania trwa wieczność...na jenkinsie to 3:38 min - znów - sporo - sporo jak na połączenie js'a, kompilację sass'a, testy i lint. Postanowiłem sprawdzić czy podbicie wersji webpacka z v3 na v4 przyniesie nam choćby minimalną korzyść. Przy okazji poznałem dwa killer-feature'y nowej wersji webpacka - mode i zero config.

mode

W wersji 4 webpacka - otrzymaliśmy przełącznik mode który automatycznie ustawia zmienną środowiskową NODE_ENV. Zmienna ta jest bardzo istotna w kontekście bundlowania np. Reacta. Jeśli nie jest ustawiona, to bundler weźmie wersję developerską biblioteki, która zawiera niezminimalizowany kod wraz z dodatkowymi ostrzeżeniami itp. itd. Aby załączyć wersję produkcyjną należy ustawić NODE_ENV=production.

Przełącznik mode robi to za nas. A co więcej - jeśli go nie podamy to z automatu ustawiany jest na production.

$ webpack --mode=development  # wersja developerska
$ webpack --mode=production   # wersja produkcyjna
$ webpack                     # wersja produkcyjna

Przykład budowania przy wykorzystaniu webpack-cli

module.exports = {
   mode: "development"
}

webpack.config.js

Dodatkowo oprócz ustawienia zmiennej NODE_ENV, przełącznik mode włącza bądź wyłącza odpowiednie pluginy odpowiedzialne np. za minifikację kodu czy ukrywanie nazw modułów:

Option Description
development Provides process.env.NODE_ENV with value development. Enables NamedChunksPlugin and NamedModulesPlugin.
production Provides process.env.NODE_ENV with value production. Enables FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin and UglifyJsPlugin.

zero config

Webpacka pamiętam jeszcze z pierwszych wersji. Niekompletna, chaotyczna dokumentacja i masa konfiguracji. Z biegiem czasu dokumentacja zyskiwała na wartości, za to konfiguracja webpacka nadal kojarzyła się z masą pracy i magii. Do czasu, kiedy to opublikowana została wersja 4 ! Od tej pory nie potrzebujemy żadnej konfiguracji. Webpack zakłada, że nasz kod znajduje się w katalogu src/index.js, a bundla ładuje do katalogu dist. Cała filozofia polega teraz na zainstalowaniu webpacka i po prostu odpaleniu go:

$ npm install wepback webpack-cli --save-dev
$ npx webpack

Tyle ! W katalogu dist znajdziesz swój "zbundlowany" kod :), którego punktem wejściowym jest plik src/index.js. Co więcej - wynikowy kod zostanie zminimalizowany, a wersje bibliotek zewnętrznych np. React'a zostaną dołączone w wersjach produkcyjnych (--mode=production by default 😎).

x2 faster?

Twórcy webpacka chwalą się, że werjsa v4 jest x2 szybsza niż v3. Niestety nie w naszym przypadku :( ...Poniżej statystyki build'ów na v3 i v4.

webpack v3:

Version: webpack 3.12.0
Time: 59373ms
             Asset      Size  Chunks                    Chunk Names
         bundle.js   2.39 MB       0  [emitted]  [big]  main
    css/styles.css    255 kB       0  [emitted]  [big]  main
     bundle.js.map   13.3 MB       0  [emitted]         main
css/styles.css.map  91 bytes       0  [emitted]         main

webpack v4:

Version: webpack 4.8.3
Time: 78105ms
             Asset      Size  Chunks                    Chunk Names
         bundle.js  2.25 MiB       0  [emitted]  [big]  main
    css/styles.css   249 KiB       0  [emitted]  [big]  main
     bundle.js.map  6.36 MiB       0  [emitted]         main
css/styles.css.map  91 bytes       0  [emitted]         main

Jak widzicie v3 zajęła ~59s podczas gdy v4 ~78s. Miało być x2 szybciej tymczasem trwało to 19s dłużej.

Ale jest inna rzecz, która powinna przykuć Waszą uwagę :) ...rozmiary bundli. Pomijając fakt, że są one za duże [big], to widzimy wyraźnie, że wersja zbudowana na webpacku-v4 jest lżejsza łącznie o jakieś 146 KB! Co więcej - source-mapa dla js'a jest lżejsza o połowę! Mniejsze rozmiary zawdzięczamy zapewne dodatkowym pluginom, które dołączone są przez przełącznik --mode=production

Wniosek ? Jeśli nie dla czasu budowania to warto podbić wersję webpacka dla rozmiaru aplikacji ;)

nodejs-6 vs nodejs-8

Ok - nie przyspieszyłem build'a poprzez wersję webpacka - to może...spróbuję z wersją nodejs'a ? Do tej pory używaliśmy v6. Teraz używamy v8? Dlaczego ?

Statystyki z jenkinsa (install, build, test, lint - łącznie):

nodejs v6 nodejs v8
3:38 min 1:41 min

Zysk: 1:57 min ! A teraz przełóżcie to sobie na ContinousIntegration i buildy odpalane przy każdym merge-requeście :) ... Mała zmiana wersji nodejs, a tyyyyyyyyyyyyyyyyyyleeeeeeeeeeeee czasu w kieszeni! Polecam ;)

Poniżej jeszcze statystyki z procesu budowania dla dwóch wersji webpacka v3 i v4 odpalonych na node-v8:

Version: webpack 3.12.0
Time: 43731ms
             Asset      Size  Chunks                    Chunk Names
         bundle.js   2.39 MB       0  [emitted]  [big]  main
    css/styles.css    255 kB       0  [emitted]  [big]  main
     bundle.js.map   13.3 MB       0  [emitted]         main
css/styles.css.map  91 bytes       0  [emitted]         main
Version: webpack 4.8.3
Time: 57687ms
             Asset      Size  Chunks                    Chunk Names
         bundle.js  2.25 MiB       0  [emitted]  [big]  main
    css/styles.css   249 KiB       0  [emitted]  [big]  main
     bundle.js.map  6.36 MiB       0  [emitted]         main
css/styles.css.map  91 bytes       0  [emitted]         main

Zysk odpowiednio:

  • 15642ms (15s) webpack-3
  • 20418ms (20s) webpack-4

NPX

Na koniec ciekawostka. Menadżer pakietów npm wraz z wersją 5.2 przychodzi w pakiecie z narzędziem npx, które to odpala skrypty zamieszczone w katalogu node_modules/.bin.

npm < 5.2

$ node ./node_modules/eslint **/*.js

Teraz można krócej :) - npm >= 5.2

$ npx eslint **/*.js

To tyle w dzisiejszym learning logu :) Zapraszam za tydzień !