dzikowski.github.io

Piszę o IT po polsku, więc z góry przepraszam za zagęszczenie kolokwializmów, ale co w angielskim brzmi naturalnie, w polskim często nie ma nawet odpowiedników.

Hello React.js

Współczesne korzystanie z JavaScriptu w aplikacjach webowych bardzo różni się od tego, co było wiele lat temu. Czasy, kiedy szukało się bibliotek, a potem wstawiało do nich linki w headerze dokumentu dawno minęły. Nic dziwnego, że Tyler McGinnis zaraz na początku swojego świetnego kursu o React.js, jeszcze przed wgłębieniem się w samego Reacta, opowiada o tym, jak działa NPM, Webpack i Babel.

React Fundamentals, bo o tym kursie mowa, to właściwie dopiero moje wejście w ekosystem Reacta. Ostatnie dwa lata, jeśli chodzi o JavaScript, spędziłem w Angularze 1.x. Ponieważ nie jestem frontentowcem, tylko full stackiem, pracę w Angularze dzieliłem z backendem i nie mam jeszcze takiego doświadczenia we froncie jak ludzie, którzy na JavaScripcie zjedli zęby. Takie pliki jak package.json, czy webpack.config.js oczywiście kojarzyłem i zmieniałem w miarę potrzeb, nigdy jednak nie wgłębiałem się do końca, co to jest, co tam w środku siedzi, do momentu, kiedy nie było mi to do czegoś potrzebne. Nigdy też nie konfigurowałem tych plików od podstaw i zamiast tego korzystałem z gotowców.

Na początku startera zająłem się konfiguracją projektu i komponentem w stylu Hello world w React.js. I własnie, zanim przywitałem się z Reactem, trzeba było ustawić konfigurację NPM i Webpacka.

(Alternatywne wprowadzenie do tego wpisu znajdziesz tutaj).

package.json

package.json to taki plik, w którym trzymane są podstawowe informacje o projekcie. Po wywołaniu w terminalu npm init można go stworzyć automatycznie, NPM tylko po drodze zapyta o kilka rzeczy: nazwę projektu, wersję, opis, polecenie wywołania testów, adres do repozytorium git i kilka innych. Oczywiście nie ma potrzeby podawania wszystkich tych rzeczy, a do części z nich sugerowane są domyślne wartości. Wygenerowany plik może wyglądać mniej więcej tak:

{
  "name": "serverless-webapp-starter",
  "version": "1.0.0",
  "description": "Serverless web application starter with Webpack, React.js and Amazon Cognito.",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/withspace/serverless-webapp-starter.git"
  },
  "author": "Jakub Dzikowski",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/withspace/serverless-webapp-starter/issues"
  },
  "homepage": "https://github.com/withspace/serverless-webapp-starter#readme"
}

Z ciekawszych rzeczy które tutaj są, to sekcja scripts. W naszym przypadku po wpisaniu w terminalu npm run test pojawi nam się tekst Error: no test specified (plus sporo, mylących na początku, komunikatów błędów NPMa, że proces zwrócił kod 1, w związku z tym sprawdź, czy masz najnowszą wersję NPM itd.).

Sekcja scripts służy zatem do ustawiania różnych konfiguracji uruchamiania projektu. Na pewno ustawimy tam jeszcze start, które będzie wykorzystywać Webpacka, ale o tym za chwilę. Najpierw o zależnościach, bo do pliku package.json dodaje się również zależności do bibliotek, z których korzysta nasz projekt. Może to wyglądać tak:

{
  "name": "serverless-webapp-starter",
  ...
  "devDependencies": {
    "babel-core": "^6.23.1",
    "babel-loader": "^6.4.0",
    "babel-preset-react": "^6.23.0",
    "html-webpack-plugin": "^2.28.0",
    "webpack": "^2.2.1",
    "webpack-dev-server": "^2.4.1"
  },
  "dependencies": {
    "react": "^15.4.2",
    "react-dom": "^15.4.2"
  }
}

Zdefiniowane są tutaj dwa rodzaje zależności: devDependencies, które są wykorzystywane tylko wewnętrznie i służą do zbudowania naszej aplikacji, odpalenia testów itp., a także dependencies, które są wymagane do tego, żeby nasza aplikacja działała (więcej możesz poczytać tutaj, mimo że to trochę stare). W naszym przypadku, żeby aplikację zbudować potrzebujemy Webpacka i Babel, przy odpalaniu na razie wystarczy nam React.

webpack.config.js

W tym pliku znajduje się konfiguracja Webpacka, czyli narzędzia, które – mówiąc w skrócie – pozbiera różne pliki źródłowe w projekcie i poskłada je w jeden, który będzie wykorzystywany na produkcji. webpack-dev-server jest natomiast bardzo użytecznym narzędziem, które pozwala odpalić aplikacje na Webpacku w trybie tzw. hot reload.

Ale wróćmy do pliku webpack.config.js, który u mnie wygląda tak:

const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlWebpackPluginConfig = new HtmlWebpackPlugin({
  template: __dirname + '/app/index.html',
  filename: 'index.html',
  inject: 'body'
});

const BabelLoader = {
  test: /\.js$/,
  exclude: /node_modules/,
  loader: "babel-loader"
};

module.exports = {
  entry: [
    './app/index.js'
  ],
  output: {
    path: __dirname + '/dist',
    filename: "index_bundle.js"
  },
  module: {
    loaders: [BabelLoader]
  },
  plugins: [HtmlWebpackPluginConfig]
};

W obiekcie dostępnym w module.exports = { ... }; zamieszcza się całą konfigurację Webpacka. Zdefiniowane są tam podstawowe rzeczy: entry, czyli główny plik z aplikacją, gdzie wszystko się zaczyna, output, czyli ścieżka i nazwa pliku wynikowego – tego, który zostanie zbudowany, a także module i plugins.

W sekcji module można zdefiniować tzw. loaders, które pozwalają na przetwarzanie plików, zanim zostaną połączone w jedną paczkę. W tym przypadku mamy tylko jeden, BabelLoader, które weźmie wszystkie pliki o rozszerzeniu .js (oprócz tych z folderu node_modules) i przetworzy je z wykorzystaniem biblioteki babel-loader. (Ale o Babelu jeszcze za chwilę).

No i mamy jeszcze plugins, które… tak w zasadzie mogą wszystko podczas procesu budowania aplikacji przez Webpacka. Tutaj na razie jest to tylko html-webpack-plugin, który wrzuca do zbudowanego pliku HTML (tego w /dist) odnośnik do zbudowanego skryptu (/dist/index_bundle.js).

.babelrc

To jest plik konfiguracyjny dla Babela. Babela, czyli chyba najpopularniejszej z bibliotek JavaScriptowych, który służy, w uproszczeniu, do takich przekształceń plików źródłowych JavaScriptu, żeby działały na wszystkich przeglądarkach. Możesz np. kodować sobie w najnowszym JavaScripcie, a Babel go tak przebuduje, że da się go odpalić na IE 9.

{
  "presets": [
    "react"
  ]
}

W naszym skromnym konfigu Babela na razie zaznaczone jest tylko to, że Babel ma obsługiwać Reacta. Znaczy to tyle, że kiedy w Webpacku wywołany będzie BabelLoader, Babel skorzysta z biblioteki babel-preset-react, żeby przerobić pliki Reactowe na czysty JavaScript, który może być obsłużony w przeglądarce.

package.json raz jeszcze

Na koniec jeszcze rozszerzymy package.json, dodając do niego dwa skrypty:

{
  ...
  "scripts": {
    "start": "webpack-dev-server --content-base app --port 9090 --inline --hot",
    "build": "webpack",
    ...
  },
  ...
}

Teraz, kiedy wywołamy w terminalu npm run start, odpali nam się serwer z działającą aplikacją w hot reload na porcie 9090. Natomiast kiedy wywołamy npm run buid, webpack zbuduje nam aplikację i zbudowane pliki wrzuci do folderu /dist.

… i wreszcie React

Na razie Reacta będzie mało, tylko komponent na Hello World, ale z użytecznym komentarzem dotyczącym wersji JavaScriptu. Bo w kursach, szczególnie tych starszych, można natknąć się na coś takiego:

var React = require('react');
var ReactDOM = require('react-dom');
 
var HelloReactJS = React.createClass({
  render: function(){
    return (<div>Serverless WebApp Starter</div>);
  }
});

ReactDOM.render(<HelloReactJS />, document.getElementById('app'));

Obecnie jednak można skorzystać z dobrodziejstw, jakie daje nam nowszy JavaScript i zamienić importy na coś ładniejszego, a z komponentu zrobić klasę. Ostatecznie plik app/index.js na początku będzie wyglądał tak:


import React from 'react';
import ReactDOM from 'react-dom';

class HelloReactJS extends React.Component {
  render() {
    return <div>Serverless WebApp Starter</div>;
  }
}

ReactDOM.render(<HelloReactJS />, document.getElementById('app'));

Po odpaleniu npm run start wchodzę na http://localhost:9090.

Wszystko pięknie działa. Mamy Hello world w React.js. 👏