04 - Webpack, React, oraz Hot Module Replacement
February 21, 2020 路 View on GitHub
Dost臋pny kod dla tego rozdzia艂u jest tutaj.
Webpack
馃挕 Webpack to module bundler. Pobiera ca艂膮 mas臋 r贸偶nych plik贸w 藕r贸d艂owych, przetwarza je i 艂膮czy w jeden (zwykle) plik JavaScript o nazwie bundle, kt贸ry jest jedynym plikiem, kt贸ry wykona klient.
Stw贸rzmy podstawowe hello world i bundle z Webpack.
- W
src/shared/config.js, dodaj nast臋puj膮ce consty:
export const WDS_PORT = 7000
export const APP_CONTAINER_CLASS = 'js-app'
export const APP_CONTAINER_SELECTOR = `.${APP_CONTAINER_CLASS}`
- Stw贸rz plik
src/client/index.jszawieraj膮cy:
import 'babel-polyfill'
import { APP_CONTAINER_SELECTOR } from '../shared/config'
document.querySelector(APP_CONTAINER_SELECTOR).innerHTML = '<h1>Hello Webpack!</h1>'
Je艣li chcesz u偶ywa膰 niekt贸rych najnowszych funkcji ES w kodzie klienta, takich jak Promise, musisz do艂膮czy膰 Babel Polyfill przed czymkolwiek innym w twoim bundle.
- Uruchom
yarn add babel-polyfill
Je艣li uruchomisz ESLint na tym pliku, b臋dzie narzeka膰 odno艣nie 偶e document jest niezdefiniowany.
- Dodaj poni偶sze do
envw twoim.eslintrc.jsonaby pozwoli膰 korzysta膰 zwindowidocument:
"env": {
"browser": true,
"jest": true
}
Dobra, teraz musimy po艂膮czy膰 t臋 aplikacj臋 klienck膮 ES6 w pakiet ES5.
- Stw贸rz plik
webpack.config.babel.jszawieraj膮cy:
// @flow
import path from 'path'
import { WDS_PORT } from './src/shared/config'
import { isProd } from './src/shared/util'
export default {
entry: [
'./src/client',
],
output: {
filename: 'js/bundle.js',
path: path.resolve(__dirname, 'dist'),
publicPath: isProd ? '/static/' : `http://localhost:${WDS_PORT}/dist/`,
},
module: {
rules: [
{ test: /\.(js|jsx)$/, use: 'babel-loader', exclude: /node_modules/ },
],
},
devtool: isProd ? false : 'source-map',
resolve: {
extensions: ['.js', '.jsx'],
},
devServer: {
port: WDS_PORT,
},
}
Ten plik s艂u偶y do opisania, w jaki spos贸b nale偶y z艂o偶y膰 nasz pakiet: entry jest punktem wyj艣cia naszej aplikacji, output.filename to nazwa pakietu do wygenerowania, output.path i output.publicPath opisz folder docelowy i adres URL. Umieszczamy pakiet w folderze dist, kt贸re b臋d膮 zawiera膰 rzeczy generowane automatycznie (w przeciwie艅stwie do deklaratywnego CSS, kt贸ry stworzyli艣my wcze艣niej, w kt贸rym istnieje public). module.rules to miejsce, w kt贸rym ka偶esz Webpackowi zastosowa膰 pewne leczenie do pewnego rodzaju plik贸w. M贸wimy tutaj, 偶e chcemy wszystkie pliki .js i .jsx (dla Reacta) opr贸cz tych w node_modules aby przej艣膰 przez co艣 zwanego babel-loader. Chcemy r贸wnie偶, aby te dwa rozszerzenia by艂y u偶ywane do 'rozwi膮zywania' modu艂贸w podczas ich 'importowania'. Wreszcie deklarujemy port dla Webpack Dev Server.
Uwaga: Rozszerzenie .babel.js to funkcja Webpack do zastosowania naszych transformacji Babel do tego pliku konfiguracyjnego.
babel-loader to wtyczka do pakietu Webpack, kt贸ra transponuje Tw贸j kod, tak jak robili艣my to od pocz膮tku tego samouczka. Jedyn膮 r贸偶nic膮 jest to, 偶e tym razem kod sko艅czy si臋 w przegl膮darce zamiast na serwerze.
- Uruchom
yarn add --dev webpack webpack-dev-server babel-core babel-loader
babel-core jest peer-dependency dla babel-loader, wi臋c te偶 to zainstalowali艣my.
- Dodaj
/dist/do swojego.gitignore
Tasks update
W trybie programowania b臋dziemy korzysta膰 z webpack-dev-server aby skorzysta膰 z Hot Module Reloading (w dalszej cz臋艣ci tego rozdzia艂u), a do produkcji po prostu skorzystamy webpack do tworzenia bundles. W obu przypadkach, flaga --progress jest przydatna do wy艣wietlania dodatkowych informacji, gdy Webpack kompiluje twoje pliki. W produkcji przeka偶emy r贸wnie偶 flag臋 -p do webpack, aby zminimalizowa膰 nasz kod, a zmienn膮 NODE_ENV ustawi膰 na production.
Zaktualizujmy nasze scripts aby wdro偶y膰 to wszystko i ulepszy膰 r贸wnie偶 niekt贸re inne zadania:
"scripts": {
"start": "yarn dev:start",
"dev:start": "nodemon -e js,jsx --ignore lib --ignore dist --exec babel-node src/server",
"dev:wds": "webpack-dev-server --progress",
"prod:build": "rimraf lib dist && babel src -d lib --ignore .test.js && cross-env NODE_ENV=production webpack -p --progress",
"prod:start": "cross-env NODE_ENV=production pm2 start lib/server && pm2 logs",
"prod:stop": "pm2 delete server",
"lint": "eslint src webpack.config.babel.js --ext .js,.jsx",
"test": "yarn lint && flow && jest --coverage",
"precommit": "yarn test",
"prepush": "yarn test && yarn prod:build"
},
W dev:start wyra藕nie deklarujemy rozszerzenia plik贸w do monitorowania, .js oraz .jsx, i dodaj dist w ignorowanych katalogach.
Stworzyli艣my osobny task lint i dodali艣my webpack.config.babel.js do plik贸w dla lint.
- Nast臋pnie utw贸rzmy kontener dla naszej aplikacji w
src/server/render-app.js, i do艂膮cz pakiet, kt贸ry zostanie wygenerowany:
// @flow
import { APP_CONTAINER_CLASS, STATIC_PATH, WDS_PORT } from '../shared/config'
import { isProd } from '../shared/util'
const renderApp = (title: string) =>
`<!doctype html>
<html>
<head>
<title>${title}</title>
<link rel="stylesheet" href="${STATIC_PATH}/css/style.css">
</head>
<body>
<div class="${APP_CONTAINER_CLASS}"></div>
<script src="${isProd ? STATIC_PATH : `http://localhost:${WDS_PORT}/dist`}/js/bundle.js"></script>
</body>
</html>
`
export default renderApp
W zale偶no艣ci od 艣rodowiska, w kt贸rym si臋 znajdujemy, do艂膮czymy Webpack Server Dev Server bundle lub pakiet produkcyjny. Zauwa偶, 偶e 艣cie偶ka do Webpack Dev Server to virtual, dist / js / bundle.js nie jest w rzeczywisto艣ci odczytywany z twojego dysku twardego w trybie programowania. Konieczne jest r贸wnie偶 podanie Webpack Dev Server innego portu ni偶 g艂贸wny port.
- Ko艅cowo, w
src/server/index.js, podkr臋膰 sw贸jconsole.logtak oto:
console.log(`Server running on port ${WEB_PORT} ${isProd ? '(production)' :
'(development).\nKeep "yarn dev:wds" running in an other terminal'}.`)
To da innym programistom wskaz贸wk臋, co zrobi膰, je艣li spr贸buj膮 po prostu uruchomi膰 yarn start bez Webpack Dev Server.
W porz膮dku, to by艂o wiele zmian, zobaczmy, czy wszystko dzia艂a zgodnie z oczekiwaniami:
馃弫 Uruchom yarn start w terminaly. Otw贸rz inn膮 kart臋 lub okno terminala i uruchom w nim yarn dev: wds. Gdy Webpack Dev Server zako艅czy generowanie pakietu i jego zasob贸w (kt贸re powinny by膰 plikami ~ 600kB) i oba procesy zawiesz膮 si臋 na twoich terminalach, otw贸rz http://localhost:8000/ i powiniene艣 zobaczy膰 'Hello Webpack!'. Otw贸rz konsol臋 Chrome i na karcie Source sprawd藕, kt贸re pliki s膮 uwzgl臋dnione. Powiniene艣 zobaczy膰 tylko static/css/style.css w localhost:8000/ i mie膰 wszystkie swoje pliki 藕r贸d艂owe ES6 w webpack://./src. Oznacza to, 偶e sourcemaps dzia艂aj膮. W swoim edytorze, w src/client/index.js, spr贸buj zmieni膰 'Hello Webpack!' na dowolny inny ci膮g. Podczas zapisywania pliku Webpack Dev Server w twoim terminalu powinien wygenerowa膰 nowy pakiet, a karta Chrome powinna si臋 ponownie za艂adowa膰 automatycznie.
- Zabij poprzednie procesy w swoich terminalach za pomoc膮 Ctrl + C, a nast臋pnie uruchom
yarn prod:build, i nast臋pnieyarn prod:start. Otw贸rzhttp://localhost:8000/i nadal powiniene艣 zobaczy膰 "Hello Webpack!". Tym razem znajdziesz na karcie Source konsoli Chromestatic/js/bundle.jspodlocalhost:8000/, ale nie 藕r贸d艂awebpack://. Kliknij nabundle.jsaby upewni膰 si臋, 偶e jest zminimalizowane. Uruchomyarn prod:stop.
Dobra robota, wiem, 偶e to by艂o do艣膰 zagmatwane. Zas艂ugujesz na przerw臋! Nast臋pna sekcja jest 艂atwiejsza.
Uwaga: Polecam mie膰 otwarte co najmniej 3 terminale, jeden dla twojego serwera Express, jeden dla Webpack Dev Server, a drugi dla Git, test贸w i og贸lnych polece艅, takich jak instalowanie pakiet贸w z yarn. Najlepiej jest podzieli膰 ekran terminala na wiele paneli, aby zobaczy膰 je wszystkie.
React
馃挕 React to biblioteka do tworzenia interfejs贸w u偶ytkownika przez Facebook. Wykorzystuje JSX sk艂adnie do reprezentowania element贸w i komponent贸w HTML przy jednoczesnym wykorzystaniu si艂y JavaScript.
W tej sekcji wyrenderujemy troch臋 tekstu przy u偶yciu React i JSX.
Najpierw zainstalujmy React i ReactDOM:
- Uruchom
yarn add react react-dom
Zmie艅 nazw臋 swojego pliku src/client/index.js na src/client/index.jsx i napisz w nim troch臋 kodu React:
// @flow
import 'babel-polyfill'
import React from 'react'
import ReactDOM from 'react-dom'
import App from './app'
import { APP_CONTAINER_SELECTOR } from '../shared/config'
ReactDOM.render(<App />, document.querySelector(APP_CONTAINER_SELECTOR))
- Stw贸rz plik
src/client/app.jsxzawieraj膮cy:
// @flow
import React from 'react'
const App = () => <h1>Hello React!</h1>
export default App
Poniewa偶 u偶ywamy tutaj sk艂adni JSX, musimy powiedzie膰 Babelowi, 偶e musi go przekszta艂ci膰 za pomoc膮 ustawienia wst臋pnego babel-preset-reag. I kiedy ju偶 nad tym pracujemy, dodamy r贸wnie偶 wtyczk臋 Babel o nazwie flow-React-proptypes, kt贸ra automatycznie generuje PropTypes z adnotacji Flow dla twoich sk艂adnik贸w React.
- Uruchom
yarn add --dev babel-preset-react babel-plugin-flow-react-proptypesi zedytuj sw贸j plik.babelrcw ten spos贸b:
{
"presets": [
"env",
"flow",
"react"
],
"plugins": [
"flow-react-proptypes"
]
}
馃弫 Uruchom yarn start i yarn dev:wds oraz wci艣nij http://localhost:8000. Powiniene艣 widzie膰 "Hello React!".
Teraz spr贸buj zmieni膰 tekst w src / client / app.jsx na co艣 innego. Webpack Dev Server powinien ponownie za艂adowa膰 stron臋 automatycznie, co jest ca艂kiem fajne, ale zamierzamy j膮 jeszcze ulepszy膰.
Hot Module Replacement
馃挕 Hot Module Replacement (HMR) to pot臋偶na funkcja Webpacka, kt贸ra zast臋puje modu艂 w locie bez ponownego 艂adowania ca艂ej strony.
Aby HMR dzia艂a艂 z React, b臋dziemy musieli ulepszy膰 kilka rzeczy.
-
Uruchom
yarn add react-hot-loader@next -
Edytuj sw贸j
webpack.config.babel.jstak oto:
import webpack from 'webpack'
// [...]
entry: [
'react-hot-loader/patch',
'./src/client',
],
// [...]
devServer: {
port: WDS_PORT,
hot: true,
headers: {
'Access-Control-Allow-Origin': '*',
},
},
plugins: [
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
],
headers ma umo偶liwi膰 udost臋pnianie zasob贸w pochodz膮cych z r贸偶nych 藕r贸de艂, co jest niezb臋dne dla HMR.
- Edytuj sw贸j plik
src/client/index.jsx:
// @flow
import 'babel-polyfill'
import React from 'react'
import ReactDOM from 'react-dom'
import { AppContainer } from 'react-hot-loader'
import App from './app'
import { APP_CONTAINER_SELECTOR } from '../shared/config'
const rootEl = document.querySelector(APP_CONTAINER_SELECTOR)
const wrapApp = AppComponent =>
<AppContainer>
<AppComponent />
</AppContainer>
ReactDOM.render(wrapApp(App), rootEl)
if (module.hot) {
// flow-disable-next-line
module.hot.accept('./app', () => {
// eslint-disable-next-line global-require
const NextApp = require('./app').default
ReactDOM.render(wrapApp(NextApp), rootEl)
})
}
Musimy uczyni膰 nasz膮 'aplikacj臋' dzieckiem 'AppContainer' 'react-hot-loader' i musimy 'wymaga膰' nast臋pnej wersji naszej 'aplikacji' podczas ponownego 艂adowania. Aby ten proces by艂 czysty i OK, tworzymy ma艂膮 funkcj臋 wrapApp, kt贸rej u偶ywamy w obu miejscach potrzebnych do renderowania App. Mo偶esz przesun膮膰 eslint-disable global-demand na g贸r臋 pliku, aby uczyni膰 go bardziej czytelnym.
馃弫 Uruchom ponownie proces yarn dev: wds, je艣li nadal by艂 uruchomiony. Otw贸rz localhost: 8000. Na karcie Konsola powinny by膰 widoczne niekt贸re dzienniki dotycz膮ce HMR. 艢mia艂o zmie艅 co艣 w src / client / app.jsx, a twoje zmiany powinny zosta膰 odzwierciedlone w przegl膮darce po kilku sekundach, bez prze艂adowywania ca艂ej strony!
Nast臋pna sekcja: 05 - Redux, Immutable, Fetch
Powr贸t do poprzedniej sekcji albo spisu tre艣ci.