d
Webpack
React oli aiemmin jossain määrin kuuluisa siitä, että sovelluskehityksen edellyttämien työkalujen konfigurointi oli hyvin hankalaa. Kiitos Create React App:in, sovelluskehitys Reactilla on kuitenkin nykyään tuskatonta. Parempaa työskentelyflow'ta on tuskin ollut koskaan JavaScriptilla tehtävässä selainpuolen sovelluskehityksessä.
Emme voi kuitenkaan turvautua ikuisesti Create React App:in magiaan, ja nyt onkin aika selvittää mitä kaikkea taustalla on. Avainasemassa React-sovelluksen toimintakuntoon saattamisessa on webpack-niminen työkalu.
Bundlaus
Olemme toteuttaneet sovelluksia jakamalla koodin moduuleihin, joita on importattu niitä tarvitseviin paikkoihin. Vaikka ES6-moduulit ovatkin JavaScript-standardissa määriteltyjä, eivät vanhemmat selaimet vielä osaa käsitellä moduuleihin jaettua koodia.
Selainta varten moduuleissa oleva koodi bundlataan, eli siitä muodostetaan yksittäinen, kaiken koodin sisältävä tiedosto. Kun veimme Reactilla toteutetun frontendin tuotantoon osan 3 luvussa Frontendin tuotantoversio, suoritimme bundlauksen komennolla npm run build. Konepellin alla kyseinen npm-skripti suorittaa bundlauksen webpackia hyväksi käyttäen. Tuloksena on joukko hakemistoon build sijoitettavia tiedostoja:
├── asset-manifest.json ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json ├── robots.txt └── static ├── css │ ├── main.1becb9f2.css │ └── main.1becb9f2.css.map └── js ├── main.88d3369d.js ├── main.88d3369d.js.LICENSE.txt └── main.88d3369d.js.map
Hakemiston juuressa oleva sovelluksen "päätiedosto" index.html lataa script-tagin avulla bundlatun JavaScript-tiedoston:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>React App</title>
<script defer="defer" src="/static/js/main.88d3369d.js"></script>
<link href="/static/css/main.1becb9f2.css" rel="stylesheet">
</head>
<div id="root"></div>
</body>
</html>
Kuten esimerkistä näemme, Create React App:illa tehdyssä sovelluksessa bundlataan JavaScriptin lisäksi sovelluksen CSS-määrittelyt tiedostoon /static/css/main.1becb9f2.css.
Käytännössä bundlaus tapahtuu siten, että sovelluksen JavaScriptille määritellään alkupiste, usein tiedosto index.js, ja bundlauksen yhteydessä webpack ottaa mukaan kaiken koodin mitä alkupiste importtaa, importattujen koodien importtaamat koodit jne.
Koska osa importeista on kirjastoja kuten React, Redux tai Axios, bundlattuun JavaScript-tiedostoon tulee myös kaikkien näiden sisältö.
Vanha tapa jakaa sovelluksen koodi moneen tiedostoon perustui siihen, että index.html latasi kaikki sovelluksen tarvitsemat erilliset JavaScript-tiedostot script-tagien avulla. Tämä on kuitenkin tehotonta, sillä jokaisen tiedoston lataaminen aiheuttaa pienen overheadin ja pääosin nykyään suositaankin koodin bundlaamista yksittäiseksi tiedostoksi.
Tehdään nyt React-projektille sopiva webpack-konfiguraatio kokonaan käsin.
Luodaan projektia varten hakemisto ja sen sisälle hakemistot (build ja src) sekä seuraavat tiedostot:
├── build ├── package.json ├── src │ └── index.js └── webpack.config.js
Tiedoston package.json sisältö voi olla esim. seuraava:
{
"name": "webpack-osa7",
"version": "0.0.1",
"description": "practising webpack",
"scripts": {},
"license": "MIT"
}
Asennetaan webpack:
npm install --save-dev webpack webpack-cli
Webpackin toiminta konfiguroidaan tiedostoon webpack.config.js. Laitetaan sen alustavaksi sisällöksi seuraava:
const path = require('path')
const config = () => {
return {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'build'),
filename: 'main.js'
}
}
}
module.exports = config
Huom: määrittely olisi mahdollista tehdä funktion sijaan myös suoraan oliona:
const path = require('path')
const config = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'build'),
filename: 'main.js'
}
}
module.exports = config
Olio riittää monissa tilanteissa, mutta tulemme myöhemmin tarvitsemaan tiettyjä ominaisuuksia, jotka edellyttävät sen että määrittely on tehty funktiona.
Määritellään sitten npm-skripti build, jonka avulla bundlaus suoritetaan:
// ...
"scripts": {
"build": "webpack --mode=development"
},
// ...
Lisätään hieman koodia tiedostoon src/index.js:
const hello = name => {
console.log(`hello ${name}`)
}
Kun nyt suoritamme komennon npm run build, webpack bundlaa koodin. Tuloksena on hakemistoon build sijoitettava tiedosto main.js:
Tiedostossa on paljon erikoisen näköistä tavaraa. Lopussa on mukana myös kirjoittamamme koodi:
eval("const hello = name => {\n console.log(`hello ${name}`)\n}\n\n//# sourceURL=webpack://webpack-osa7/./src/index.js?");
Lisätään hakemistoon src tiedosto App.js ja sille sisältö:
const App = () => {
return null
}
export default App
Importataan moduuli App ja käytetään sitä tiedostossa index.js:
import App from './App';
const hello = name => {
console.log(`hello ${name}`)
}
App()
Kun nyt suoritamme bundlauksen komennolla npm run build, huomaamme webpackin havainneen molemmat tiedostot:
Kirjoittamamme koodi löytyy erittäin kryptisesti muotoiltuna bundlen lopusta:
Konfiguraatiotiedosto
Katsotaan nyt tarkemmin konfiguraation webpack.config.js tämänhetkistä sisältöä:
const path = require('path')
const config = () => {
return {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'build'),
filename: 'main.js'
}
}
}
module.exports = config
Konfiguraatio on JavaScriptia ja tapahtuu eksporttaamalla määrittelyt palauttava funktio Noden moduulisyntaksilla.
Tämänhetkinen minimaalinen määrittely on aika ilmeinen. Kenttä entry kertoo sen tiedoston, mistä bundlaus aloitetaan.
Kenttä output taas kertoo minne muodostettu bundle sijoitetaan. Kohdehakemisto täytyy määritellä absoluuttisena polkuna, mikä onnistuu helposti path.resolve-metodilla. __dirname on Noden globaali muuttuja, joka viittaa nykyiseen hakemistoon.
Reactin bundlaaminen
Muutetaan sitten sovellus minimalistiseksi React-sovellukseksi. Asennetaan tarvittavat kirjastot:
npm install react react-dom
Liitetään tavanomaiset loitsut tiedostoon index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
ReactDOM.createRoot(document.getElementById('root')).render(<App />)
ja muutetaan App.js muotoon
import React from 'react' // tarvitsemme importin nyt myös kompontentin määrittelyn yhteydessä
const App = () => {
return (
<div>
hello webpack
</div>
)
}
export default App
Tarvitsemme sovellukselle myös "pääsivuna" toimivan tiedoston build/index.html, joka lataa script-tagin avulla bundlatun JavaScriptin:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>React App</title>
</head>
<body>
<div id="root"></div>
<script type="text/javascript" src="./main.js"></script>
</body>
</html>
Kun bundlaamme sovelluksen, törmäämme kuitenkin ongelmaan:
Loaderit
Webpack mainitsee, että saatamme tarvita loaderin tiedoston App.js käsittelyyn. Webpack ymmärtää itse vain JavaScriptia, ja vaikka se saattaa meiltä matkan varrella olla unohtunutkin, käytämme Reactia ohjelmoidessamme JSX:ää näkymien renderöintiin, eli esim. seuraava
const App = () => {
return (
<div>
hello webpack
</div>
)
}
ei ole "normaalia" JavaScriptia, vaan JSX:n tarjoama syntaktinen oikotie määritellä div-tagia vastaava React-elementti.
Loaderien avulla on mahdollista kertoa webpackille miten tiedostot tulee käsitellä ennen niiden bundlausta.
Määritellään projektiimme Reactin käyttämän JSX:n normaaliksi JavaScriptiksi muuntava loaderi:
const path = require('path')
const config = () => {
return {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'build'),
filename: 'main.js'
},
module: { rules: [ { test: /\.js$/, loader: 'babel-loader', options: { presets: ['@babel/preset-react'], }, }, ], }, }
}
module.exports = config
Loaderit määritellään kentän module alle sijoitettavaan taulukkoon rules.
Yksittäisen loaderin määrittely on kolmiosainen:
{
test: /\.js$/,
loader: 'babel-loader',
options: {
presets: ['@babel/preset-react']
}
}
Kenttä test määrittelee, että käsitellään .js-päätteisiä tiedostoja. Kenttä loader kertoo, että käsittely tapahtuu Babel Loader:illa. Kenttä options taas antaa loaderille sen toimintaa ohjaavia parametreja.
Asennetaan loader ja sen tarvitsemat kirjastot kehitysaikaisiksi riippuvuuksiksi:
npm install @babel/core babel-loader @babel/preset-react --save-dev
Nyt bundlaus onnistuu.
Jos katsomme bundlattua koodia ja editoimme hieman koodin ulkoasua, huomaamme, että komponentti App on muuttunut muotoon
const App = () =>
react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(
'div',
null,
'hello webpack'
)
Eli JSX-syntaksin sijaan komponentit luodaan pelkällä JavaScriptilla käyttäen Reactin funktiota createElement.
Sovellusta voi nyt kokeilla avaamalla tiedoston build/index.html selaimen open file -toiminnolla:
On kuitenkin huomionarvoista, että jos sovelluksemme sisältää async/await-toiminnallisuutta, selaimeen ei joillain selaimilla renderöidy mitään. Konsoliin tulostuneen virheviestin googlaaminen valaisee asiaa. Ongelma korjaantuu asentamalla kirjastot core-js ja regenerator-runtime
npm install core-js regenerator-runtime
ja importtaamalla ne tiedostossa index.js:
import 'core-js/stable/index.js'
import 'regenerator-runtime/runtime.js'
Tässä on jo melkein kaikki mitä tarvitsemme React-sovelluskehitykseen.
Transpilaus
Prosessista, joka muuttaa JavaScriptia muodosta toiseen käytetään englanninkielistä termiä transpiling, joka taas on termi, joka viittaa koodin kääntämiseen (compile) sitä muuntamalla (transform). Suomenkielisen termin puuttuessa käytämme prosessista tällä kurssilla nimitystä transpilaus.
Edellisen luvun konfiguraation avulla siis transpiloimme JSX:ää sisältävän JavaScriptin normaaliksi JavaScriptiksi tämän hetken johtavan työkalun Babelin avulla.
Kuten osassa 1 jo mainittiin, läheskään kaikki selaimet eivät vielä osaa JavaScriptin uusimpien versioiden ES6:n ja ES7:n ominaisuuksia, ja tämän takia koodi yleensä transpiloidaan käyttämään vanhempaa JavaScript-syntaksi ES5:ttä.
Babelin suorittama transpilointiprosessi määritellään pluginien avulla. Käytännössä useimmiten käytetään valmiita presetejä eli useamman sopivan pluginin joukkoja.
Tällä hetkellä sovelluksemme transpiloinnissa käytetään presetiä @babel/preset-react:
{
test: /\.js$/,
loader: 'babel-loader',
options: {
presets: ['@babel/preset-react'] }
}
Otetaan käyttöön preset @babel/preset-env, joka sisältää kaiken hyödyllisen, minkä avulla uusimman standardin mukainen koodi saadaan transpiloitua ES5-standardin mukaiseksi koodiksi:
{
test: /\.js$/,
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react'] }
}
Preset asennetaan komennolla
npm install @babel/preset-env --save-dev
Kun nyt transpiloimme koodin, muuttuu se vanhan koulukunnan JavaScriptiksi. Komponentin App määrittely näyttää seuraavalta:
var App = function App() {
return _react2.default.createElement('div', null, 'hello webpack')
};
Muuttujan määrittely tapahtuu avainsanan var avulla, sillä ES5 ei tunne avainsanaa const. Myöskään nuolifunktiot eivät ole käytössä, joten funktiomäärittely käyttää avainsanaa function.
CSS
Lisätään sovellukseemme hieman CSS:ää. Tehdään tiedosto src/index.css:
.container {
margin: 10;
background-color: #dee8e4;
}
Määritellään tyyli käytettäväksi komponentissa App
const App = () => {
return (
<div className="container">
hello webpack
</div>
)
}
ja importataan se tiedostossa index.js:
import './index.css'
Transpilointi hajoaa:
CSS:ää varten onkin otettava käyttöön css- ja style-loaderit:
{
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
options: {
presets: ['@babel/preset-react', '@babel/preset-env'],
},
},
{ test: /\.css$/, use: ['style-loader', 'css-loader'], }, ];
}
css-loaderin tehtävänä on ladata CSS-tiedostot, ja style-loader generoi koodiin CSS:t sisältävän style-elementin.
Näin konfiguroituna CSS-määrittelyt sisällytetään sovelluksen JavaScriptin sisältävään tiedostoon main.js. Sovelluksen päätiedostossa index.html ei siis ole tarvetta erikseen ladata CSS:ää.
CSS voidaan tarpeen vaatiessa myös generoida omaan tiedostoonsa esim. mini-css-extract-pluginin avulla.
Kun loaderit asennetaan
npm install style-loader css-loader --save-dev
bundlaus toimii taas ja sovellus saa uudet tyylit.
Webpack-dev-server
Sovelluskehitys onnistuu jo, mutta development workflow on suorastaan hirveä (alkaa jo muistuttaa Javalla tapahtuvaa sovelluskehitystä...). Muutosten jälkeen koodi on bundlattava ja selain uudelleenladattava jos haluamme testata koodia.
Ratkaisun tarjoaa webpack-dev-server. Asennetaan se komennolla
npm install --save-dev webpack-dev-server
Määritellään dev-serverin käynnistävä npm-skripti:
{
// ...
"scripts": {
"build": "webpack --mode=development",
"start": "webpack serve --mode=development" },
// ...
}
Lisätään tiedostoon webpack.config.js kenttä devServer:
const config = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'build'),
filename: 'main.js',
},
devServer: { static: path.resolve(__dirname, 'build'), compress: true, port: 3000, }, // ...
};
Komento npm start käynnistää nyt dev-serverin porttiin, eli sovelluskehitys tapahtuu avaamalla tuttuun tapaan selain osoitteeseen http://localhost:3000. Kun teemme koodiin muutoksia, reloadaa selain automaattisesti itsensä.
Päivitysprosessi on nopea, sillä dev-serveriä käytettäessä webpack ei bundlaa koodia normaaliin tapaan tiedostoksi main.js, vaan bundlauksen tuotos on olemassa ainoastaan keskusmuistissa.
Laajennetaan koodia muuttamalla komponentin App määrittelyä seuraavasti:
import React, { useState } from 'react'
import './index.css'
const App = () => {
const [counter, setCounter] = useState(0)
return (
<div className="container">
hello webpack {counter} clicks
<button onClick={() => setCounter(counter + 1)}>
press
</button>
</div>
)
}
export default App
Sovellus toimii hyvin ja kehitys on melko sujuvaa.
Sourcemappaus
Erotetaan napin klikkauksenkäsittelijä omaksi funktiokseen ja talletetaan tilaan values laskurin aiemmat arvot:
const App = () => {
const [counter, setCounter] = useState(0)
const [values, setValues] = useState()
const handleClick = () => {
setCounter(counter + 1)
setValues(values.concat(counter)) }
return (
<div className="container">
hello webpack {counter} clicks
<button onClick={handleClick}> press
</button>
</div>
)
}
Sovellus ei enää toimi, ja konsoli kertoo virheestä:
Tiedämme tietenkin nyt, että virhe on metodissa onClick, mutta jos olisi kyse suuremmasta sovelluksesta, on virheilmoitus sikäli hyvin ikävä, että sen ilmoittama paikka
App.js:27 Uncaught TypeError: Cannot read property 'concat' of undefined at handleClick (App.js:27)
ei vastaa alkuperäisen koodin virheen sijaintia. Jos klikkaamme virheilmoitusta, huomaamme, että näytettävä koodi on jotain ihan muuta kuin kirjoittamamme koodi:
Haluamme tietenkin, että virheilmoitusten yhteydessä näytetään kirjoittamamme koodi.
Korjaus on onneksi hyvin helppo. Pyydetään webpackia generoimaan bundlelle ns. source map, jonka avulla bundlea suoritettaessa tapahtuva virhe on mahdollista mäpätä alkuperäisen koodin vastaavaan kohtaan.
Source map saadaan generoitua lisäämällä konfiguraatioon kenttä devtool ja sen arvoksi 'source-map':
const config = {
entry: './src/index.js',
output: {
// ...
},
devServer: {
// ...
},
devtool: 'source-map', // ..
};
Konfiguraatioiden muuttuessa webpack tulee käynnistää uudelleen. On tosin mahdollista konfiguroida webpack tarkkailemaan konfiguraatioiden muutoksia, mutta emme tee sitä.
Nyt virheilmoitus on hyvä
sillä se viittaa itse kirjoittamaamme koodiin:
Source mapin käyttö mahdollistaa myös Chromen debuggerin luontevan käytön:
Korjataan bugi alustamalla tila values tyhjäksi taulukoksi:
const App = () => {
const [counter, setCounter] = useState(0)
const [values, setValues] = useState([])
// ...
}
Koodin minifiointi
Kun sovellus viedään tuotantoon, on siis käytössä tiedostoon main.js webpackin generoima koodi. Vaikka sovelluksemme sisältää omaa koodia vain muutaman rivin, on tiedoston main.js koko 1009487 tavua, sillä se sisältää myös kaiken React-kirjaston koodin. Tiedoston koollahan on sikäli väliä, että selain joutuu lataamaan tiedoston kun sovellusta aletaan käyttämään. Nopeilla internetyhteyksillä 1009487 tavua ei sinänsä ole ongelma, mutta jos mukaan sisällytetään enemmän kirjastoja, alkaa sovelluksen lataaminen pikkuhiljaa hidastua etenkin mobiilikäytössä.
Tiedoston sisältöä tarkastelemalla huomaa, että tiedostoa voisi optimoida huomattavasti koon suhteen esim. poistamalla kommentit. Tiedostoa ei kuitenkaan kannata lähteä optimoimaan käsin, sillä tarkoitusta varten on olemassa monia työkaluja.
JavaScript-tiedostojen optimointiprosessista käytetään nimitystä minifiointi. Alan johtava työkalu tällä hetkellä lienee UglifyJS.
Webpackin versiosta 4 alkaen pluginia ei ole tarvinnut konfiguroida erikseen. Riittää, että muutetaan tiedoston package.json määrittelyä siten, että koodin bundlaus tapahtuu production-moodissa:
{
"name": "webpack-osa7",
"version": "0.0.1",
"description": "practising webpack",
"scripts": {
"build": "webpack --mode=production", "start": "webpack serve --mode=development"
},
"license": "MIT",
"dependencies": {
// ...
},
"devDependencies": {
// ...
}
}
Kun sovellus bundlataan uudelleen, pienenee tuloksena oleva main.js mukavasti:
$ ls -l build/main.js
-rw-r--r-- 1 mluukkai ATKK\hyad-all 146237 Feb 7 15:58 build/main.js
Minifioinnin lopputulos on kuin vanhan liiton C-koodia. Kommentit ja jopa turhat välilyönnit ja rivinvaihdot on poistettu ja muuttujanimet ovat yksikirjaimisia:
function h(){if(!d){var e=u(p);d=!0;for(var t=c.length;t;){for(s=c,c=[];++f<t;)s&&s[f].run();f=-1,t=c.length}s=null,d=!1,function(e){if(o===clearTimeout)return clearTimeout(e);if((o===l||!o)&&clearTimeout)return o=clearTimeout,clearTimeout(e);try{o(e)}catch(t){try{return o.call(null,e)}catch(t){return o.call(this,e)}}}(e)}}a.nextTick=function(e){var t=new Array(arguments.length-1);if(arguments.length>1)
Sovelluskehitys- ja tuotantokonfiguraatio
Lisätään sovellukselle backend. Käytetään jo tutuksi käynyttä muistiinpanoja tarjoavaa palvelua.
Talletetaan seuraava sisältö tiedostoon db.json:
{
"notes": [
{
"important": true,
"content": "HTML is easy",
"id": "5a3b8481bb01f9cb00ccb4a9"
},
{
"important": false,
"content": "Mongo can save js objects",
"id": "5a3b920a61e8c8d3f484bdd0"
}
]
}
Tarkoituksena on konfiguroida sovellus webpackin avulla siten, että paikallisesti sovellusta kehitettäessä käytetään backendina portissa 3001 toimivaa JSON Serveriä.
Bundlattu tiedosto laitetaan sitten käyttämään todellista, osoitteessa https://notes2023.fly.dev/api/notes olevaa backendia.
Asennetaan Axios, käynnistetään JSON Server ja tehdään tarvittavat lisäykset sovellukseen. Vaihtelun vuoksi muistiinpanojen hakeminen palvelimelta on toteutettu custom hookin useNotes avulla:
import React, { useState, useEffect } from 'react'
import axios from 'axios'
const useNotes = (url) => { const [notes, setNotes] = useState([]) useEffect(() => { axios.get(url).then(response => { setNotes(response.data) }) }, [url]) return notes}
const App = () => {
const [counter, setCounter] = useState(0)
const [values, setValues] = useState([])
const url = 'https://notes2023.fly.dev/api/notes'
const notes = useNotes(url)
const handleClick = () => {
setCounter(counter + 1)
setValues(values.concat(counter))
}
return (
<div className="container">
hello webpack {counter} clicks
<button onClick={handleClick} >press</button>
<div>{notes.length} notes on server {url}</div> </div>
)
}
export default App
Koodissa on nyt kovakoodattuna sovelluskehityksessä käytettävän palvelimen osoite. Miten saamme osoitteen hallitusti muutettua osoittamaan Internetissä olevaan backendiin bundlatessamme koodin?
Webpackin konfiguraatiofunktiolla on kaksi parametria, env ja argv, joista jälkimmäisen avulla saamme selville npm-skriptissä määritellyn moden:
const path = require('path')
const config = (env, argv) => { console.log('argv.mode:', argv.mode)
return {
// ...
}
}
module.exports = config
Nyt voimme siis halutessamme säätää Webpackin toimimaan eri tavalla riippuen siitä onko sovelluksen käyttöympäristö eli "mode" arvoltaan production vai development.
Webpackin DefinePlugin:in avulla voimme määritellä globaaleja vakioarvoja, joita on mahdollista käyttää bundlattavassa koodissa. Määritellään nyt vakio BACKEND_URL, joka saa eri arvon riippuen siitä ollaanko kehitysympäristössä vai tehdäänkö tuotantoon sopivaa bundlea:
const path = require('path')
const webpack = require('webpack')
const config = (env, argv) => {
console.log('argv.mode:', argv.mode)
const backend_url = argv.mode === 'production' ? 'https://notes2023.fly.dev/api/notes' : 'http://localhost:3001/notes'
return {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'build'),
filename: 'main.js'
},
devServer: {
static: path.resolve(__dirname, 'build'),
compress: true,
port: 3000,
},
devtool: 'source-map',
module: {
// ...
},
plugins: [ new webpack.DefinePlugin({ BACKEND_URL: JSON.stringify(backend_url) }) ] }
}
module.exports = config
Määriteltyä vakiota käytetään koodissa seuraavasti:
const App = () => {
const [counter, setCounter] = useState(0)
const [values, setValues] = useState([])
const notes = useNotes(BACKEND_URL)
// ...
return (
<div className="container">
hello webpack {counter} clicks
<button onClick={handleClick} >press</button>
<div>{notes.length} notes on server {BACKEND_URL}</div> </div>
)
}
Nyt siis jos sovellus on käynnistetty komennolla npm start development-moodissa, hakee se muistiinpanot osoitteesta http://localhost:3001/notes. Komennolla npm run build bundlattu versio taas käyttää osoitetta https://notes2023.fly.dev/api/notes muistiinpanojen hakemiseen.
Jos kehitys- ja tuotantokonfiguraatio eriytyvät paljon, saattaa olla hyvä idea eriyttää konfiguraatiot omiin tiedostoihinsa.
Tuotantoversiota eli bundlattua sovellusta on mahdollista kokeilla lokaalisti suorittamalla komento
npx static-server
hakemistossa build, jolloin sovellus käynnistyy oletusarvoisesti osoitteeseen http://localhost:9080.
Polyfill
Sovelluksemme on valmis ja toimii muiden selaimien kohtuullisen uusilla versiolla, mutta Internet Explorerilla sovellus ei toimi. Syynä tähän on se, että Axiosin ansiosta koodissa käytetään Promiseja, mutta mikään IE:n versio ei kuitenkaan niitä tue:
On paljon muitakin standardissa määriteltyjä asioita, joita IE ei tue. Esim. niinkin harmiton komento kuin taulukoiden find ylittää IE:n kyvyt:
Tälläisessä tilanteessa normaali koodin transpilointi ei auta, sillä transpiloinnissa koodia käännetään uudemmasta JavaScript-syntaksista vanhempaan, selaimien paremmin tukemaan syntaksiin. Promiset ovat syntaktisesti täysin IE:n ymmärrettävissä, IE:ltä vain puuttuu toteutus Promisesta. Samoin on tilanne taulukoiden suhteen, IE:llä taulukoiden find on arvoltaan undefined.
Jos haluamme sovelluksen IE-yhteensopivaksi, tarvitsemme polyfilliä eli koodia, joka lisää puuttuvan toiminnallisuuden vanhempiin selaimiin.
Polyfillaus on mahdollista hoitaa Webpackin ja Babelin avulla tai asentamalla yksi monista tarjolla olevista polyfill-kirjastoista.
Esim. kirjaston Promise Polyfill tarjoaman polyfillin käyttö on todella helppoa lisäämällä koodiin seuraava:
import PromisePolyfill from 'promise-polyfill'
if (!window.Promise) {
window.Promise = PromisePolyfill
}
Jos globaalia Promise-olioa ei ole olemassa eli selain ei tue Promiseja, sijoitetaan polyfillattu Promise globaaliin muuttujaan. Jos polyfillattu Promise on hyvin toteutettu, muun koodin pitäisi toimia ilman ongelmia.
Kattavahko lista olemassaolevista polyfilleistä löytyy täältä.
Selaimien yhteensopivuus käytettävien API:en suhteen kannattaakin tarkistaa esim. https://caniuse.com-sivustolta tai Mozillan sivuilta.
Eject
Create React App käyttää taustalla webpackia. Jos peruskonfiguraatio ei riitä, on projektit mahdollista ejektoida, jolloin kaikki konepellin alla oleva magia häviää, ja konfiguraatiot tallettuvat hakemistoon config ja muokattuun package.json-tiedostoon.
Jos Create React App:illa tehdyn sovelluksen ejektoi, paluuta ei ole, vaan sen jälkeen kaikesta konfiguroinnista on huolehdittava itse. Konfiguraatiot eivät ole triviaaleimmasta päästä, ja Create React Appin ja ejektoinnin sijaan parempi vaihtoehto saattaa joskus olla tehdä koko webpack-konfiguraatio itse.
Ejektoidun sovelluksen konfiguraatioiden lukeminen on suositeltavaa ja sangen opettavaista!