Articles

Sätt upp ett ES6-projekt med hjälp av Babel och webpack

I den här artikeln ska vi titta på hur vi skapar en bygguppsättning för att hantera modern JavaScript (som körs i webbläsare) med hjälp av Babel och webpack.

Detta behövs för att se till att vår moderna JavaScript-kod i synnerhet blir kompatibel med ett bredare utbud av webbläsare än vad som annars skulle kunna vara fallet.

JavaScript, liksom de flesta webbrelaterade tekniker, utvecklas hela tiden. På den gamla goda tiden kunde vi släppa in ett par <script>-taggar i en sida, kanske inkludera jQuery och ett par plugins, och sedan vara redo att köra.

Däremot har saker och ting blivit alltmer komplicerade sedan införandet av ES6. Webbläsarstödet för nyare språkfunktioner är ofta fragmentariskt, och i takt med att JavaScript-applikationer blir mer ambitiösa börjar utvecklarna använda moduler för att organisera sin kod. Detta innebär i sin tur att om du skriver modernt JavaScript idag måste du införa ett byggsteg i din process.

Som du kan se på länkarna nedan ökar konverteringen ner från ES6 till ES5 dramatiskt antalet webbläsare som vi kan stödja.

  • ES6-kompatibilitet
  • ES5-kompatibilitet

Syftet med ett byggsystem är att automatisera det arbetsflöde som behövs för att få vår kod redo för webbläsare och produktion. Detta kan omfatta steg som att transpila kod till en annan standard, kompilera Sass till CSS, bunta ihop filer, minifiera och komprimera kod och många andra. För att säkerställa att dessa är konsekvent repeterbara behövs ett byggsystem för att initiera stegen i en känd sekvens från ett enda kommando.

Förutsättningar

För att kunna följa med måste du ha både Node.js och npm installerade (de levereras paketerade tillsammans). Jag rekommenderar att du använder en versionshanterare som nvm för att hantera din Node-installation (så här gör du), och om du vill ha hjälp med att komma igång med npm kan du läsa SitePoints nybörjarvänliga npm-tutorial.

Sätt upp

Skapa en rotmapp någonstans på din dator och navigera in i den från din terminal/kommandorad. Detta blir din <ROOT>-mapp.

Skapa en package.json-fil med detta:

npm init -y

Notera: Flaggan -y skapar filen med standardinställningar och innebär att du inte behöver fylla i några av de vanliga detaljerna från kommandoraden. De kan ändras i din kodredigerare senare om du vill.

I din <ROOT>-mapp skapar du katalogerna src, src/js och public. Mappen src/js kommer att vara den plats där vi lägger vår obearbetade källkod, och mappen public kommer att vara den plats där den transpilerade koden kommer att hamna.

Transpilering med Babel

För att komma igång kommer vi att installera babel-cli, som ger oss möjlighet att transpila ES6 till ES5, och babel-preset-env, som gör det möjligt att rikta in oss på specifika webbläsarversioner med den transpilerade koden.

npm install babel-cli babel-preset-env --save-dev

Du bör nu se följande i din package.json:

"devDependencies": { "babel-cli": "^6.26.0", "babel-preset-env": "^1.6.1"}

Medans vi är i package.json-filen, låt oss ändra scripts-avsnittet så att det ser ut så här:

"scripts": { "build": "babel src -d public"},

Det här ger oss möjligheten att anropa Babel via ett skript, istället för direkt från terminalen varje gång. Om du vill veta mer om npm-skript och vad de kan göra kan du läsa den här SitePoint-tutorialen.

Slutligt, innan vi kan testa om Babel gör sin grej, måste vi skapa en .babelrc konfigurationsfil. Det är den som vårt babel-preset-env-paket kommer att hänvisa till för sina transpileringsparametrar.

Skapa en ny fil i din <ROOT>-katalog som heter .babelrc och klistra in följande i den:

{ "presets": } } ] ]}

Detta kommer att ställa in Babel så att den transpilerar för de två senaste versionerna av varje webbläsare, plus Safari i v7 eller högre. Andra alternativ finns tillgängliga beroende på vilka webbläsare du behöver stödja.

Med detta sparat kan vi nu testa saker och ting med en exempel-JavaScript-fil som använder ES6. För den här artikeln har jag modifierat en kopia av leftpad så att den använder ES6-syntax på ett antal ställen: malllitteraler, pilfunktioner, const och let.

Spara detta som src/js/leftpad.js och kör följande från terminalen:

npm run build

Om allt är som det var tänkt bör du nu hitta en ny fil med namnet js/leftpad.js i din public-mapp. Om du öppnar den ser du att den inte längre innehåller någon ES6-syntax och ser ut så här:

Organisera din kod med ES6-moduler

En ES6-modul är en JavaScript-fil som innehåller funktioner, objekt eller primitiva värden som du vill göra tillgängliga för en annan JavaScript-fil. Du export från den ena och import in i den andra. Alla seriösa moderna JavaScript-projekt bör överväga att använda moduler. De gör det möjligt för dig att dela upp din kod i fristående enheter och därmed göra det lättare att underhålla, de hjälper dig att undvika förorening av namnrymden och de bidrar till att göra din kod mer portabel och återanvändbar.

Men medan majoriteten av ES6-syntaxen är allmänt tillgänglig i moderna webbläsare, är detta ännu inte fallet med moduler. I skrivande stund är de tillgängliga i Chrome, Safari (inklusive den senaste iOS-versionen) och Edge; de är dolda bakom en flagga i Firefox och Opera; och de är inte tillgängliga (och kommer troligen aldrig att bli det) i IE11, och inte heller i de flesta mobila enheter.

I nästa avsnitt ska vi titta på hur vi kan integrera moduler i vår bygguppsättning.

Export

Nyckelordet export gör det möjligt för oss att göra våra ES6-moduler tillgängliga för andra filer, och det ger oss två alternativ för att göra det – namngiven och standard. Med named export kan du ha flera exporter per modul och med default export kan du bara ha en export per modul. Namngiven export är särskilt användbar när du behöver exportera flera värden. Du kan till exempel ha en modul som innehåller ett antal hjälpfunktioner som måste göras tillgängliga på olika ställen i dina appar.

Så låt oss förvandla vår leftPad-fil till en modul, som vi sedan kan kräva i en andra fil.

Namngiven export

För att skapa en namngiven export lägger du till följande längst ner i leftPad-filen:

export { leftPad };

Vi kan också ta bort "use strict";-deklarationen högst upp i filen, eftersom moduler körs i strikt läge som standard.

Defult Export

Då det bara finns en enda funktion som ska exporteras i leftPad-filen kan det faktiskt vara en bra kandidat för att använda export default i stället:

export default function leftPad(str, len, ch) { ...}

Även här kan du ta bort "use strict";-deklarationen från toppen av filen.

Import

För att kunna använda exporterade moduler måste vi nu importera dem till den fil (modul) vi vill använda dem i.

För alternativet export default kan den exporterade modulen importeras under valfritt namn. Till exempel kan modulen leftPad importeras så här:

import leftPad from './leftpad';

Och den kan importeras under ett annat namn, så här:

import pineapple_fritter from './leftpad';

Funktionellt sett kommer båda att fungera exakt likadant, men det är självklart vettigt att använda antingen samma namn som den exporterades under, eller något som gör att importen blir begriplig – kanske där det exporterade namnet skulle kollidera med ett annat variabelnamn som redan finns i den mottagande modulen.

För alternativet namngiven export måste vi importera modulen med samma namn som den exporterades under. För vår exempelmodul skulle vi importera den på ett liknande sätt som vi använde med export default-syntaxen, men i det här fallet måste vi omsluta det importerade namnet med hakparenteser:

import { leftPad } from './leftpad';

Hackparenteserna är obligatoriska med en namngiven export, och den kommer att misslyckas om de inte används.

Det är möjligt att ändra namnet på en namngiven export vid import om det behövs, och för att göra det måste vi ändra vår syntax lite med hjälp av import as -syntaxen. Precis som med export finns det en mängd olika sätt att göra detta på, som alla beskrivs i detalj på MDN:s sida om import.

import { leftPad as pineapple_fritter } from './leftpad_es6';

När det gäller namnbytet är det lite nonsens, men det illustrerar poängen att de kan ändras till vad som helst. Du bör alltid hålla dig till god namngivningspraxis, såvida du naturligtvis inte skriver rutiner för att förbereda fruktbaserade recept.

Konsumera den exporterade modulen

För att använda den exporterade leftPad-modulen har jag skapat följande index.js-fil i mappen src/js. Här loopar jag genom en array av serienummer och sätter nollor före dem för att göra dem till en sträng med åtta tecken. Senare kommer vi att använda oss av detta och lägga ut dem i ett ordnat listelement på en HTML-sida. Observera att det här exemplet använder standardsyntaxen för export:

Som vi gjorde tidigare, kör byggskriptet från katalogen <ROOT>:

npm run build

Babel kommer nu att skapa en index.js-fil i katalogen public/js. Precis som med vår leftPad.js-fil bör du se att Babel har ersatt all ES6-syntax och lämnat kvar endast ES5-syntax. Du kanske också märker att den har konverterat ES6-modulsyntaxen till den Node-baserade module.exports, vilket innebär att vi kan köra den från kommandoraden:

node public/js/index.js// 

Din terminal bör nu logga ut en matris av strängar som är prefixerade med nollor för att göra dem alla åtta tecken långa. Med detta gjort är det dags att ta en titt på webpack.

Introducing webpack and Integrating it with Babel

Som nämnts tillåter ES6-moduler JavaScript-utvecklaren att bryta upp sin kod i hanterbara delar, men konsekvensen av detta är att dessa delar måste serveras till den begärande webbläsaren, vilket potentiellt kan lägga till dussintals ytterligare HTTP-förfrågningar tillbaks till servern – något som vi verkligen borde försöka undvika. Det är här webpack kommer in.

webpack är ett modulpaket. Dess primära syfte är att bearbeta ditt program genom att spåra alla dess beroenden och sedan paketera dem alla till ett eller flera paket som kan köras i webbläsaren. Den kan dock vara mycket mer än så, beroende på hur den konfigureras.

webpack-konfigurationen bygger på fyra nyckelkomponenter:

  • en ingångspunkt
  • en utdataplats
  • laddare
  • plugins

Ingångspunkt: Detta är startpunkten för din applikation från vilken webpack kan identifiera sina beroenden.

Utgång: Detta är startpunkten för din applikation från vilken webpack kan identifiera sina beroenden: Detta anger var du vill att det bearbetade paketet ska sparas.

Loaders: Detta anger var du vill att det bearbetade paketet ska sparas: Dessa är ett sätt att konvertera en sak som input och generera något annat som output. De kan användas för att utöka webpacks möjligheter att hantera mer än bara JavaScript-filer, och därför konvertera dessa till giltiga moduler också.

Plugins: Dessa används för att utöka webpacks kapacitet till andra uppgifter än buntning – till exempel minifiering, linting och optimering.

För att installera webpack kör du följande från din <ROOT>-katalog:

npm install webpack webpack-cli --save-dev

Detta installerar webpack lokalt i projektet och ger också möjlighet att köra webpack från kommandoraden genom att lägga till webpack-cli. Du bör nu se webpack listat i din package.json-fil. Medan du är i den filen ändrar du avsnittet scripts enligt följande, så att det nu vet att det ska använda webpack istället för Babel direkt:

"scripts": { "build": "webpack --config webpack.config.js"},

Som du kan se anropar det här skriptet en webpack.config.js-fil, så låt oss skapa den i vår <ROOT>-katalog med följande innehåll:

Det här är mer eller mindre den enklaste konfigurationsfilen du behöver med webpack. Du kan se att den använder entry- och output-sektionerna som beskrivits tidigare (den skulle kunna fungera enbart med dessa), men den innehåller också en mode: 'development'-inställning.

webpack har möjlighet att använda antingen ”development”- eller ”production”-lägen. Inställningen mode: 'development' optimerar för bygghastighet och felsökning, medan mode: 'production' optimerar för exekveringshastighet vid körning och storlek på utdatafilen. Det finns en bra förklaring av lägen i Tobias Koppers artikel ”webpack 4: mode and optimization” om du vill läsa mer om hur de kan konfigureras utöver standardinställningarna.

Nästan tar du bort alla filer från mappen public/js. Kör sedan den här igen:

npm run build

Du kommer att se att den nu innehåller en enda ./public/bundle.js-fil. Öppna dock den nya filen och de två filerna vi började med ser ganska annorlunda ut. Detta är den del av filen som innehåller index.js-koden. Även om den är ganska kraftigt modifierad från vårt original kan du fortfarande urskilja dess variabelnamn:

Om du kör node public/js/bundle.js från mappen <ROOT> ser du att du får samma resultat som vi hade tidigare.

Transpiling

Som tidigare nämnts tillåter loaders oss att omvandla en sak till något annat. I det här fallet vill vi att ES6 konverteras till ES5. För att göra det behöver vi ytterligare ett par paket:

npm install babel-loader babel-core --save-dev

För att använda dem behöver webpack.config.js en modulsektion som läggs till efter outputsektionen, så här:

Detta använder ett regex-uttalande för att identifiera de JavaScript-filer som ska transpileras med babel-loader, samtidigt som allt i mappen node_modules utesluts från detta. Slutligen får babel-loader order om att använda babel-preset-env-paketet som installerats tidigare för att fastställa de transpileringsparametrar som anges i .babelrc-filen.

Med detta gjort kan du köra om detta:

npm run build

Kontrollera sedan den nya public/js/bundle.js och du kommer att se att alla spår av ES6-syntaxen har försvunnit, men att den fortfarande ger samma utdata som tidigare.

Tillbaka till webbläsaren

När vi har byggt en fungerande webpack- och Babeluppsättning är det dags att ta med det vi har gjort till webbläsaren. En liten HTML-fil behövs, och den ska skapas i mappen <ROOT> enligt nedan:

Det behövs lite mer JavaScript för att visa listan, så låt oss ändra ./src/js/index.js för att få det att hända:

Om du nu öppnar index.html i din webbläsare bör du se en ordnad lista visas så här:

Taking it Further

Som konfigurerat ovan är vårt byggsystem i stort sett redo att användas. Vi kan nu använda webpack för att bunta våra moduler och transpila ES6-kod ner till ES5 med Babel.

Det är dock lite jobbigt att vi, för att transpila vår ES6-kod, måste köra npm run build varje gång vi gör en ändring.

Att lägga till en ”watch”

För att komma till rätta med behovet av att köra npm run build upprepade gånger kan du ställa in en 'watch' på dina filer och få webpack att kompilera om automatiskt varje gång den ser en ändring i en av filerna i mappen ./src. För att implementera detta ändrar du avsnittet scripts i filen package.json enligt nedan:

"scripts": { "watch": "webpack --watch", "build": "webpack --config webpack.config.js"},

För att kontrollera att det fungerar kör du npm run watch från terminalen och du kommer att se att den inte längre återgår till kommandotolken. Gå nu tillbaka till src/js/index.js och lägg till ett extra värde i arrayen serNos och spara det. Min ser nu ut så här:

const serNos = ;

Om du nu kontrollerar terminalen ser du att den har loggat ut och att den har kört om uppgiften webpack build. Om du går tillbaka till webbläsaren och uppdaterar ser du att det nya värdet har lagts till i slutet av listan efter att ha behandlats med leftPad.

Förny webbläsaren automatiskt

Det skulle vara riktigt bra om vi kunde få webpack att uppdatera webbläsaren automatiskt varje gång vi gör en ändring. Låt oss göra det genom att installera ett extra npm-paket som heter webpack-dev-server. Glöm dock inte att först Ctrl + c ur uppgiften watch!

npm install webpack-dev-server --save-dev

Med det gjort, låt oss lägga till ett nytt skript i filen package.json för att anropa det nya paketet. Avsnittet scripts bör nu innehålla detta:

Märk flaggan --open-page som lagts till i slutet av skriptet. Detta talar om för webpack-dev-server att öppna en specifik sida i din standardwebbläsare med hjälp av dess iframe-läge.

Kör nu npm start och du bör se att en ny webbläsarflik öppnas på med delförteckningen som visas. För att visa att 'watch' fungerar går du till src/js/index.js och lägger till ytterligare ett nytt värde i slutet av arrayen serNos. När du sparar dina ändringar bör du märka att de återspeglas nästan omedelbart i webbläsaren.

Med detta klart är det enda som återstår att ställa in läget i webpack.config.js till production. När detta är inställt kommer webpack också att minifiera koden som den matar ut i ./public/js/bundle.js. Du bör notera att om mode inte är inställd kommer webpack som standard att använda konfigurationen production.

Slutsats

I den här artikeln har du sett hur du sätter upp ett byggsystem för modern JavaScript. Inledningsvis användes Babel från kommandoraden för att konvertera ES6-syntaxen ner till ES5. Du har sedan sett hur man använder ES6-moduler med nyckelorden export och import, hur man integrerar webpack för att utföra en buntningsuppgift och hur man lägger till en watch-uppgift för att automatisera körning av webpack varje gång ändringar i en källfil upptäcks. Slutligen har du sett hur du installerar webpack-dev-server för att uppdatera sidan automatiskt varje gång en ändring görs.

Om du vill gå vidare med detta föreslår jag att du läser SitePoints djupdykning i webpack och modulbuntning, samt att du forskar om ytterligare laddare och plugins som gör det möjligt för webpack att hantera Sass och komprimeringsuppgifter för tillgångar. Titta också på eslint-loader och insticksmodulen för Prettier också.

Happy bundling …