En komponents filer
En komponent ska gå att flytta. Kopierar du dess katalog till ett annat projekt ska den fungera där också, utan att du behöver leta upp bilder, stilar eller registreringskod på andra ställen. Den här artikeln visar hur kursens komponenter är organiserade för att det ska vara möjligt. Det är samma struktur som i övningsuppgifterna och som examinationsuppgift B2 kräver.
Tidsåtgång: Ungefär 12 minuter.
En komponent, en katalog
Section titled “En komponent, en katalog”Varje komponent har en egen katalog under src/js/components/, med samma namn som elementet. Så här
ser projektet i övningsuppgiften Tiny Tunes ut när tiny-time är klar:
Mappsrc
Mappcss
- styles.css
Mappjs
Mappcomponents
Mapptiny-time
- index.js registrerar elementet
- README.md dokumenterar komponenten
- tiny-time.css stilarna i shadow DOM
- tiny-time.html uppmärkningen i shadow DOM
- tiny-time.js klassen
Mapptiny-tune
- …
- index.js applikationens startpunkt
- index.html
Varje fil har ett enda ansvar. HTML skrivs i en .html-fil och CSS i en .css-fil, så att editorn,
lintern och formateraren behandlar dem som HTML och CSS. Klassen ligger i en fil med komponentens
namn, och index.js gör bara en sak: registrerar elementet.
HTML och CSS som strängar
Section titled “HTML och CSS som strängar”Överst i tiny-time.js importeras uppmärkningen och stilarna:
import htmlText from './tiny-time.html?raw'import cssText from './tiny-time.css?inline'
const template = document.createElement('template')template.innerHTML = htmlText
const stylesheet = new CSSStyleSheet()stylesheet.replaceSync(cssText)?raw och ?inline är inte en del av JavaScript. Det är tillägg som Vite förstår: de säger åt Vite
att ge dig filens innehåll som en vanlig sträng. Vite skriver om importerna innan koden når
webbläsaren, så det blir inga extra förfrågningar, och i produktionsbygget ligger strängarna direkt
i den paketerade koden.
Koden fungerar alltså bara tillsammans med Vite. Det är ett medvetet val, eftersom alla kursens projekt redan använder Vite. Alternativen du kan stöta på står sig så här:
- CSS-moduler,
import stylesheet from './x.css' with { type: 'css' }, är standard och ger direkt ettCSSStyleSheet. Vite kan i dagsläget inte bygga dem, men det kan ändras. - HTML-moduler,
with { type: 'html' }, finns inte. Idén har utforskats men lagts ner. fetch()av filerna fungerar utan Vite, men komponenten kan inte registreras förrän filerna har hämtats. Under tiden står elementet odefinierat på sidan, och varje sidladdning kostar extra förfrågningar.
Övningsuppgiften bart-board i kursvecka 11 har en fil, ARCHITECTURE.md, som går igenom valet mer
utförligt.
Klassen och registreringen
Section titled “Klassen och registreringen”I introduktionen i kursvecka 8
skickades en klass utan namn direkt till customElements.define(). För ett första exempel räcker
det. I kursens komponenter är klassen i stället namngiven och exporterad från en egen fil, och
registreringen sker i index.js:
export class TinyTime extends HTMLElement { // ...}import { TinyTime } from './tiny-time.js'
if (!customElements.get('tiny-time')) { customElements.define('tiny-time', TinyTime)}Skälet är regeln från första artikeln: ett namn och en
klass kan bara registreras en gång per sida. Om tiny-time.js själv anropade define() skulle varje
import av klassen också registrera den. Med uppdelningen kan klassen importeras utan att något
registreras, till exempel av en testfil som registrerar den under ett eget, tillfälligt namn. Så är
testerna i bart-board skrivna.
Kontrollen med customElements.get() skyddar mot att index.js körs mer än en gång, vilket kan
hända om samma fil importeras via två olika sökvägar som paketeraren inte känner igen som samma fil.
Att ladda komponenten
Section titled “Att ladda komponenten”Applikationen laddar komponenten med en import utan namn:
import './components/tiny-tune/index.js'import './components/tiny-time/index.js'En sådan import körs bara för sin sidoeffekt: modulen körs, och define() anropas. Inget behöver
importeras med namn. Eftersom en modul körs först när alla dess importer har körts är komponenterna
garanterat registrerade när resten av index.js körs, oavsett i vilken ordning skripten står i HTML-
koden.
Skriv ut hela sökvägen, med index.js och filändelsen. Du kan stöta på kod som bara pekar på
katalogen, import './components/tiny-time/'. Det fungerar i Vite, men är ett arv från CommonJS i
Node.js. Varken webbläsaren eller ES-moduler i Node.js letar upp index.js i en katalog, och en
sökväg som bara fungerar tack vare paketeraren döljer vilken fil som faktiskt laddas.
Fråga: vad händer utan importen?
Section titled “Fråga: vad händer utan importen?”Du har skrivit klart tiny-time och lagt till den i index.html:
<div id="step09"> <tiny-time></tiny-time></div>Men du har glömt raden import './components/tiny-time/index.js' i src/js/index.js. Vad ser du på sidan,
och vad står i konsolen?
Ingenting syns, och konsolen är tom.
Ingen kod har importerat tiny-time/index.js, så den körs aldrig och define() anropas aldrig.
<tiny-time> är ett giltigt namn för ett custom element, så webbläsaren skapar ett odefinierat
element utan innehåll och väntar på en registrering som aldrig kommer. Det är inget fel enligt
webbläsaren, så inget felmeddelande visas.
Ser ditt element tomt ut, kontrollera först i utvecklarverktygen om det har en #shadow-root. Saknas
den har komponenten inte registrerats.
Komponentens egna resurser
Section titled “Komponentens egna resurser”En komponent som visar en standardbild behöver en sökväg till bilden. En sökväg som /images/default.png
utgår från webbplatsens rot och slutar fungera så fort komponenten flyttas till ett projekt där
bilden inte ligger på samma plats. Bilden hör till komponenten och ska ligga i dess katalog, med en
sökväg som utgår från komponentens egen fil.
Det standardiserade sättet är import.meta.url, som ger den aktuella modulens egen URL, tillsammans
med URL-klassen:
const defaultAvatar = new URL('./images/default.png', import.meta.url)// http://localhost:5173/js/components/user-card/images/default.pngnew URL() räknar ut en relativ sökväg på samma sätt som webbläsaren gör med en länk. Vite känner
igen mönstret och tar med bilden i produktionsbygget, så länge sökvägen är en fast sträng. I Vite kan
du också importera bilden direkt, import defaultAvatar from './images/default.png', och få en URL
tillbaka. Båda sätten gör komponenten flyttbar.
Bilder som hör till sidan i stort, som bilderna i steg 5 i Tiny Tunes, ligger i stället i public/
och nås med en sökväg som börjar med /.
Fråga: vilken adress blir det?
Section titled “Fråga: vilken adress blir det?”Modulen ligger på http://localhost:5173/js/components/user-card/user-card.js. Vad ger
new URL('../shared/icon.svg', import.meta.url).hrefhttp://localhost:5173/js/components/shared/icon.svg
Den relativa sökvägen räknas från katalogen som modulen ligger i, .../components/user-card/. ..
går upp till .../components/, och därifrån till shared/icon.svg. Det är samma regler som för
en relativ länk i HTML.
Lägg också märke till att en komponent som pekar ut ur sin egen katalog inte längre är helt flyttbar.
Beroenden till npm-paket
Section titled “Beroenden till npm-paket”En komponent kan använda npm-paket som all annan kod. tiny-tune spelar sina toner med paketet Tone.js:
import * as Tone from 'tone'Paketet installeras med npm install och står i projektets package.json. Det kopieras inte in i
komponentens katalog. Vite hittar det i node_modules och paketerar det tillsammans med resten av
koden. Vill du flytta komponenten till ett annat projekt behöver du alltså även lägga till beroendet
där, och därför ska komponentens dokumentation nämna det.
Dokumentationen
Section titled “Dokumentationen”En komponent används av andra än den som skrev den, och därför är dess README.md en del av
komponenten. Den beskriver komponentens publika gränssnitt, allt som syns utifrån. Den skrivs, precis
som koden, på engelska: dokumentationen följer med komponenten dit den flyttas, och är en förlängning
av koden snarare än en text om den.
# user-card
Displays the name and description of a user.
## Attributes
### `name`
The user's name. Default: `Unknown`.
## Methods
None.
## Events
None.
## Styling with CSS
| Part or custom property | Description || --- | --- || `::part(name)` | The heading containing the name. || `--user-card-name-color` | The color of the name. |
## Example
```html<user-card name="Ada Lovelace"> <p>Wrote the first published computer program.</p></user-card>```bart-board har en README.md med samma uppbyggnad, och det har även tiny-time i lösningen till
Tiny Tunes. I examinationsuppgift B2 är en README.md per komponent ett krav.
Tumregler
Section titled “Tumregler”Några tumregler som gör en komponent lätt att använda:
- Bete dig som de inbyggda elementen. Kalla ett attribut för en bildadress
src, intesource. Ge elementetdisplay: blockoch respekterahidden. - Ha rimliga standardvärden. En komponent ska fungera utan att varje attribut är satt. Det är
bättre att den visar
Unknownän att den kastar ett fel. - Låt användaren bestämma över elementet självt. Komponenten ändrar i sitt eget shadow-träd,
men lägger inte till klasser eller attribut på sig själv som användaren inte bett om. Behöver
elementet en roll för skärmläsare, till exempel
role="timer", sätts den medElementInternals:this.attachInternals().role = 'timer'i konstruktorn, i stället för medsetAttribute(). - Data ner, händelser upp. Komponenten får data via attribut och metoder, och meddelar omvärlden med egna händelser. Det är ämnet för kursvecka 11.
- Samla allt i katalogen. HTML, CSS, bilder och dokumentation ligger hos komponenten. Bara npm-paket ligger utanför.
Att ta med sig
Section titled “Att ta med sig”- En komponent, en katalog under
src/js/components/, med<namn>.html,<namn>.css,<namn>.js,index.jsochREADME.md. ?rawoch?inlineär Vite-tillägg som importerar filernas innehåll som strängar.- Klassen exporteras från
<namn>.js. Baraindex.jsanroparcustomElements.define(), och bara om namnet inte redan är registrerat. - Applikationen laddar komponenten med
import './components/<namn>/index.js', med hela sökvägen. - Komponentens egna resurser nås relativt modulen, med
new URL(..., import.meta.url)eller en import.
Vidare läsning
Section titled “Vidare läsning”- Läsanvisningarna för veckan, i synnerhet kapitel 10.3.7 och 11.9 i kursboken.
- import.meta och URL på MDN.
- Static Asset Handling i Vites dokumentation, om
?raw,?inlineochnew URL(..., import.meta.url). tiny-tune/README.mdi övningsuppgiften Tiny Tunes, som ett exempel på dokumentation.