FI 2
Hacklab Helsinki #web-kehitys
kurssi 21.4.2021 -
Kuva: Wikimedia Commons, CC BY-AS 4.0
Tällä kurssikerralla:
- pyöritään hakemistossa
packages/app
- tutustutaan Vue.js 3 -alustaan
- tutustutaan selainpuolen routtaukseen ("client-side routing") ja sen toteutukseen Vue Router -kirjastolla
Jos aika riittää:
- käynnistetään Cypress ja ajetaan sen testit
Ensin juuritasolla:
$ git pull
$ npm install
Sitten frontin hakemistossa:
$ cd packages/app
$ npm install
Katsotaan hakemiston sisältöä hetki IDE:ssä:
Käydään läpi tämän tason tiedostot / hakemistot
package.json
Aina node-moduuleilla on parasta vilkaista package.json
ensin. Mitä riippuvuuksia, mitä komentoja, files
ja exports
-kentät.
App
sisältää päätason Vue-komponentin ja sovelluksessa kaikkialla näkyvät osaset (esim. login)
pages
sisältää eri tilanteissa näkyvän sisällön. Vue Routing määrittää, mikä näistä on näytöllä.
data
sisältää Firestore -tietokannan sitomisen Vue:n (tai kulloisenkin UI-alustan) reaktiivisiksi muuttujiksi.
firebase
, tools
tarpeellisia työkaluja, jotta kokonaisuus pysyisi pystyssä.
app.js
etc.
Juuritasolla on aplikaation käyntiin laittamiseksi tarvittavia tiedostoja: app.js
, router.js
, user.js
. Ne eivät sisällä kovin sovelluskeskeistä asiaa, vaan se on Vue-komponenteissa.
Hakemistossa App
(päätaso) ja pages
(sovelluksen sisältö) on Vue-komponentit, jotka määräävät mitä sovellus tekee ja miltä se näyttää.
Pidetään UI ja IDE rinnakkain auki.
*Katsotaan esim. ylävasemman ("YOUR LOGO HERE!") elementin sisältöä selaimessa (oikea klikki > Tarkista
).
Etsitään tuo lähdekoodista
Käydään komponentti läpi
-
Vue-komponentit käsittävät HTML:n (
template
), CSS:nstyle
ja ohjelmapuolenscript
.- style:t ovat yleensä
scoped
, eli eivät vuoda toisiin komponentteihin. Tämä lyhentää myös niiden määrittelyä. - osiot voivat olla missä vain järjestyksessä mutta template-style-script on usein käytetty
- komponentin skriptin lopussa on
export default { ... }
, jonka kentät ovat määrämuotoisia
- style:t ovat yleensä
Tämä on yksinkertainen komponentti.
Katsotaan toista.
Valitse yksi kerrallaan ja käydään läpi:
- yläoikean käyttäjän nimen näyttävä komponentti (jos olet kirjautuneena)
-
pages/Home/index.vue
eli alkunäyttö
Näillä kierroksilla luodaan huomiota esim.
v-if
jav-for
-rakenteisiin, eli siihen, miten Vue näkyy lähdekoodissa.
Reaktiivinen ohjelmointi (Wikipedia)
Web-sovelluksen kohdalla reaktiivisuus tarkoittaa esim. sitä, että tietokannassa tapahtuvat muutokset reititetään sovelluksen läpi ruudulla ilman varsinaista aktiivista ohjelmointia. Reitti laitetaan pystyyn ja sen jälkeen UI vain maagisesti päivittyy, jos taustajärjestelmän tiedot muuttuvat.
Jokainen moderni web-kehitysalusta tukee reaktiivisuutta, tosin kukin omalla tavallaan.
Nämä eroavat toistaan siten, että:
-
- voi käyttää kaikille tietotyypeille
- varsinainen arvo luetaan/asetetaan
.value
-syntaksilla -
template
-osassa (HTML).value
:n voi jättää pois; JavaScriptissä se tarvitaan aina
-
- voi käyttää vain objekteille:
reactive({ count: 0 })
- kenttiä voi käyttää suoraan (
.count
), myös JavaScript-puolella
- voi käyttää vain objekteille:
Reactive
ja Ref
ovat kuin kaksi eri ohjelmointirajapintaa samaan asiaan, ja on kurssin pitäjän mielestä vähän vaivauttavaa, että tarjolla on molemmat. Missään ei suoraan sanota, milloin toinen olisi toista parempi.
Esimerkkisovelluksessa päädyttiin käyttämään vain Ref
:jä.
Tarkemmin sanottuna
shallowRef
, jolloin objektimuotoisen arvon alakentät eivät ole reaktiivisia. Ero on pieni - käytännössäshallowRef
on kevyempi.
Tarkastellaan, miten reaktiivisuus näkyy esimerkkiohjelmassa.
Sukelletaan koodiin
pages/Home/index.vue
sisältää rivin:const [projectsRef, unsub] = activeProjects(uid); // start following this user's projects
activeProjects
löytyydata/activeProjects.js
:tä`activeProjects` -määrittely
function activeProjects(uid) { // (string) => Ref of Map of -> { ..projectsC doc } // Firestore does not allow '.where()' on missing fields (Mar 2021); we want projects without '.removed'. // Let them come and skip the ones not wanted. // const [mapRef,unsub] = collRef( collection(db, 'projects/'), where('members', 'array-contains', uid), { conv: (v) => v.removed ? null : v // filter out removed already at the porch }); return [mapRef,unsub]; }
collection
jawhere
määrittelevät, mitä Firestore:n osaa seurataan.collRef
on apukoodia, joka kuuntelee Firestorea ja muuttaa kuulemansa Vue.js:nRef
:ksi.
unsub
on funktio, jota pitää kutsua, jos sovellus ei enää ole kiinnostunut seuraamaan tietokantaa (esim. sovelluksen sisäistä näkymää vaihdettaessa).
Sorkitaan dataa!
Nyt, kun tiedämme mistä
Ref
saa tietonsa, voidaan kokeilla:
- avata emulaattorin UI:n (
localhost:4000
) ja muuttaa siellä tietoja Voit riskittä lisätä, poistaa, vaihtaa tietoja. Päivittyykö sovelluksen UI vastaavasti?
Tekemäsi muutokset ovat käytössä niin kauan, kunnes käynnissä oleva emulaattori (npm run dev
) suljetaan.
Uudella käynnistyksellä alkutiedot luetaan uudestaan (ks. local/docs.js
).
Olemme nyt tutustuneet Vue.js -komponentteihin käytännössä ja reaktiiviseen tiedon kulkuun tietokannasta selaimeen.
Seuraavaksi katsotaan, miten selain ylipäänsä päätyy käyttämään ensimmäistä komponenttiaan.
src
-hakemiston juureen on jätetty joitain ohjelmatiedostoja.
Nämä ovat pääosin sovelluksen logiikasta (joka elää Vue-komponenteissa) riippumattomia, mutta tarpeellisia, jotta kaikki toimii.
app.js
Tämä sisältää sovelluksen "entry point"in. init
-funktiota kutsutaan, kun Firebase on saatu käyntiin, joten sovelluksella on jo tietty toimintaympäristö valmiina käynnistyessään (tämä yksinkertaistaa sen koodia).
Lisäksi tämä taso lataa Vue.js:n komponenttipuun (createApp(App)
) ja käynnistää routtauksen hallinnan (app.use(router)
).
router.js
Tämä on kenties lähdekooditiedostoista maagisin. Lukemalla Vue Router Guide:n kahteen tai viiteen kertaan voi ehkä saada käsitystä yksityiskohdista (kurssin pitäjä on vähän tuskastunut tämän kulman kanssa..).
Olannaisinta on ymmärtää selainpuolisen routtauksen konsepti (seuraavassa kappaleessa).
Käytännössä tämä osa tiedostosta on se sovelluksen kannalta olennaisin (kommentit poistettu):
const routes = [
rLocked('/', Home ),
rOpen('/', HomeGuest, { name: 'Home.guest' }),
rLocked('/projects/:id', Project), // '/projects/<project-id>[&user=...]'
{ path: '/index.html', redirect: '/' },
rOpen('/:pathMatch(.*)', NotFound )
];
Tämä määrittelee seuraavat reitit:
-
/
päänäyttö -
/
vierailijanäyttö (kun käyttäjä ei ole kirjautunut) -
/projects/:id
tietyn projektin sisältö
Lisäksi index.html
ohjautuu päänäyttöön ja muille reiteille näytetään 404-ruutu.
Aloitetaanpa tarinalla.
Paljon aikaisemmin, kun serverit lähettivät selaimelle sivuja, ne olivat vastuussa myös noiden sivujen osoitteista. Olet nähnyt näitä osoitteita selaimen osoitepalkissa. Joka sivu ladattiin uudelleen, mutta koska ne olivat kevyitä ja Internet ei ollut vielä kovin vanha, tämä kaikki oli mahdollista.
Sittemmin Internet kasvoi, ja käyttäjät halusivat muutakin kuin taulukoita täytettäväkseen ja Submit
-napin painettavakseen. Tieto alkoi kulkea Ajax:na serverin ja selaimen välillä silloinkin, kun käyttäjä ei tehnyt kerrassaan mitään.
Käyttäjät pitivät tästä. Lopputulemana se, mikä ennen oli "sivusto" olikin muuttunut "sovellukseksi". Sovellus osasi käynnistää itsensä nopeasti ja ladata lisää ominaisuuksia matkan varrella, jos niille oli tarvis. Serveri ei kuitenkaan enää määrännyt osoitepalkissa näkyvistä reiteistä. Selain teki nyt sen.
Selain on siis vastuussa siitä, mitä tapahtuu, kun palvelimelta pyydetään esim. sun.sovellus.com/projekti/5207
.
Tällöin tapahtuu seuraavaa:
-
Firebase hosting (tai kuka domainia
sun.sovellus.com
hostaakin, lähettää selaimelleindex.html
:n.Miksi
index.html
?Koska selaimella ei ole vielä tietoa sovelluksesta. Voi olla, että käyttäjä sai linkin kaveriltaan tai netistä, eikä ole koskaan aiemmin käyttänyt sovellusta. Serveri siis "pelaa varman päälle" ja lähettää saman paketin riippumatta syvemmästä reitistä.
-
Selain saa
index.html
:n ja kerää siinä mainitut skriptit jne. ja käynnistää sovelluksen. -
Sovelluksen sisäinen reititin (Vue Router) havaitsee pyydetyn polun (
/projekti/5207
) ja katsoo, löytyykö sitä vastaava Vue-komponentti.Tässä vaiheessa tulee mm. autentikaatio mukaan. Jos polkuun pääsy on suojattu (ks. yllä
rLocked
-funktio), ja kyseinen käyttäjä ei ole autentikoitunut(*), hänet ohjataankin ensin autentikoitumaan ja sen jälkeen pyytämälleen sivulle.(*) Tämä taas selviää esim. selaimen sisäisen, saittikohtaisen muistin perusteella. Firebase Auth tallettaa viimeaikaiset autentikoinnit IndexedDB -kantaan, mutta muitakin vaihtoehtoja on. (kurssin kannalta liian syvillä vesillä.. 🏊♂️🦈)
-
Jos Vue-komponentti löytyy, Vue Router päivittää selaimen
router-view
-tagin sisältämään sen. Tämä tag löytyy aina jostain Vue-ohjelmassa (esimerkissäApp/index.vue
).
Paina sovelluksen 'Jolly Jumper' -projektia
Mitä tapahtuu? Mitä tiedät edellä olevan suhteen tapahtuneen?
Voidaan myös lisätä
debugger;
-komento johonkin kohtaan reitityksen koodia ja katsoa, miten selaimen suoritus pysähtyy sinne.
Reittien suunnittelulla on myös syvempi merkitys. Ne ovat osa sovelluksen käyttäjälle näkyvää rajapintaa.
Otetaan esimerkiksi /projekti/5207
. Käyttäjä voi sen avulla:
- kiinnittää suoran linkin selaimen kirjanmerkkeihin
- jakaa linkin esim. sähköpostin tai pikaviestimen välityksellä muille
Sovellusta suunnitellessa kannattaakin jo alussa miettiä, miten se jakautuu eri osioihin ja mitkä niistä ansaitsevat ovan reittinsä.
Vinkki: Jos päädyt vaihtamaan reittejä matkan varrella, voit käyttää
redirect
-toimintaa niin, että vanhatkin reitit toimivat (jos mahdollista). Näin käyttäjien kirjanmerkit pysyvät toiminnassa pitempään.
Tämä riittää lähdekoodiin tutustumiselle.
Mikäli haluat kehittää omaa sovellustasi, voit aloittaa sen tältä pohjalta. Tee muutoksia lähdekoodiin, ja tutustu Vue.js:n dokumentointiin siinä sivussa.
Kurssin jatkokerrat käsittelevät sitä, miten sovellus pakataan tuotantokäyttöön, toimitetaan pilveen ja miten sitä seurataan (ja parannetaan) sen ollessa jo käyttäjien saatavilla.
Lisäaineistoa:
- Basics of Composition API (Vue.js docs)
- Vue Router (v4) (Vue Router docs)
Seuraavaksi: 2.1 Cypress-käyttöliittymätestaus