Sergi Papaseit Valls
Sergi Papaseit Valls Web- en .NET nerd/Co-founder
| 5 reactie(s)

KnockoutJs met SystemJs, ES2015, Jasmine en Karma in ASP.NET MVC 6 en Visual Studio 2015: The bleeding edge of Web Development

Of a bleeding edge, tenminste. Want het Web is een wereld van veel veranderingen.

Note: If you want you can also read this article in English

Een dagje weg van de SPA

De motivatie voor deze post kwam voort uit onze ervaring met Aurelia, een Single Page Application (SPA) framework dat SystemJs als een module loader gebruikt, in combinatie met BabelJs als transpiler. Dit maakt het mogelijk om vandaag al met ECMAScript 2015 te kunnen werken zonder je zorgen te maken over browserondersteuning, of gebrek daaraan.

Maar hoe hip het ook mag zijn, niet elke applicatie is een geschikte SPA kandidaat, ook al kunnen SPAs uit meer dan één pagina bestaan.
Bij CodeNamed vroegen we ons dus af, hoe dit allemaal zou werken in een “gewone”, multi-paged MVC 6 applicatie? And down the rabbit hole we went. Deze post ontstond toen we het licht aan het einde van het bovengenoemde gat zagen en de stappen wilden documenteren van wat, in veel momenten, een kronkelend pad is geweest, zo dat jij, geachte lezer, ongedeerd erin en eruit kunt lopen. Tenminste, totdat het weer tijd is om de packages die we gaan gebruiken te updaten. Welkom in de wereld van web development.

De setup

Ik wil allereerst duidelijk maken dat deze post niet gaat over hoe je KnockoutJs gebruikt, of hoe je tests met Jasmine schrijft. Ik ga je niet MVC 6 noch de nieuwe ES2015 syntax leren. Deze blog post gaat over hoe je al deze technologieën samen kunt gebruiken.

Nu dat duidelijk is, open Visual Studio 2015 en…

  1. File / New Project
  2. Selecteer de .NET Framweork 4.6.1 en ASP.NET Web Application
  3. In de ASP.NET 5 Templates, selecteer Web Application
  4. In wwwroot, maak een src folder aan. Hier gaan we onze JavaScript broncode plaatsen.
  5. In src, maak een viewmodels folder aan.
  6. In src, maak een pages folder aan.
  7. Buiten wwwroot, maak een test folder aan.

De beste manier om SystemJs te installeren en er mee te werken is gebruik maken van jspm.

jspm is a package manager for the SystemJS universal module loader, built on top of the dynamic ES6 module loader

Open een Command Prompt, ga naar de root-folder van het project (één niveau hoger dan wwwroot) en installeer en initialiseer jspm als volgt:

De init command zal een aantal vragen stellen. Bij de meeste kunt je simpelweg op Enter drukken om de default optie te selecteren. Let gewoon op dat baseUrl naar ./wwwroot wijst en dat je babel als transpiler selecteert.

Dit creëert een jspm_packages folder en een config.js in de wwwroot folder, en een package.json net daarbuiten.

Voordat we door kunnen moeten we SystemJs laten weten waar hij onze JavaScript bestanden kan vinden. Open config.js en voeg de volgende regel toe aan de paths configuration sectie:

De sectie in het geheel zou er als volgt uit moeten zien:

Ook binnen config.js, vind en verwijder de volgende regel:

De applicatie heeft deze baseUrl setting niet nodig, en het zou ons eigenlijk in de weg zitten door Karma naar de verkeerde folder te sturen op zoek naar onze JavaScript bestanden.

Tijd om wat packages te installeren

jspm staat het toe om packages uit de npm repository te installeren, en zelfs direct uit GitHub, wat best wel handig is gezien niet alle packages in de jspm repo te vinden zijn. Zoals, bijvoorbeeld, KnockoutJs en jQuery:

In beide gevallen zeggen we tegen jspm dat de packages met een alias geïnstalleerd dienen te worden (jquery= and knockout= respectievelijk). Deze alias zal handig zijn wanneer we de KnockoutJs en jQuery packages willen importeren.

Komt de code nog?

Ok, hoe gebruiken we dus SystemJs en BabelJs?

1. _Layout.cshtml

Open the _Layout.cshtml en voeg net voor de sluiting van de tag, de volgende code toe:

Hier zeggen we tegen SystemJS dat hij de main module moet laden. SystemJs zal in de wwwroot/src folder op zoek naar een main.js bestand gaan (zoals we in config.js gedefinieerd hebben).

2. main.js

main.js zal niet veel anders doen dan de modules importeren welke over de gehele applicatie gebruikt zullen worden, zoals KnockoutJs or jQuery. Zo hoeven we deze niet steeds te importeren in elke module. Keep it DRY 😉

Vanaf nu hebben de rest van de modules een centraal punt om de ko en $ variabelen te definiëren.

Het idee is dat er voor elke MVC View een module (een JavaScript bestand) met dezelfde naam komt (de naam hoeft niet hetzelfde te zijn, het is gewoon voor de duidelijkheid). Deze modules zullen extreem simpel zijn en zullen alleen drie taken uitvoeren:

  1. De Main module importeren.
  2. Het KnockoutJs viewmodel voor de huidige view importeren, eveneens als andere mogelijke benodigde modules, zoals bijvoorbeeld utility classes.
  3. ko.applyBindings aanroepen om de view up and running te krijgen.

3. Index.cshtml

Als eerste moet Index.cshtml dus de module importeren welke de KnockoutJs bindings zal toepassen (deze gaan we zo direct aanmaken). Het moet ook wat html definiëren, uiteraard.
Om het simpel te houden zal de view alleen Naam en Achternaam inputs tonen en de volledige naam onderin tonen. Dit is de view in al haar glorie:

Zoals je kunt zien heb ik iets van Bootstrap 3 classes geprobeerd toe te passen en heb de Knockout bindings toegepast op de invoervelden, but that’s where the excitement ends.

4. fullName.js

De volgende stap is om het viewmodel te creëren dat naar de Index.cshtml view zal binden. Ik heb die in de src/viewmodels folder geplaatst. Dit viewmodel is net zo eenvoudig als de view zelf: het definieert alleen de drie benodigde properties in de class constructor.

5. index.js

We gaan nu de module toevoegen welke Index.cshtml importeert om de KnockoutJs bindings toe te passen. Plaats deze in de pages folder binnen src.

Je vraag je wellicht af waarom zo’n “extra” bestand nodig is. Kunnen we niet ko.applyBindings(...) vanuit het viewmodel aanroepen? Simpel: Vanwege unit testing. applyBindings aanroepen vanuit een bestand met vrijwel geen code betekent dat

  1. We dat bestand niet hoeven te testen, en
  2. We hoeven de ko object niet in elke spec te mocken.

In mijn eerste poging riep ik ko.applybindings aan vanuit het viewmodel, maar omdat KnockoutJs in de main module wordt geïmporteerd in plaats van in de fullName module, waar het daadwerkelijk gebruikt wordt, werd er door de test runner een “ko is not defined” exception gegooid; en KnockoutJs importeren in de test file hielp niet.
Ik moet zeggen, ik vind deze setup eigenlijk beter. Het is misschien overkill voor een kleine demo applicatie, maar het scheelt een hoop wanneer je aan een grote applicatie werkt waar KnockoutJs in vrijwel elke module gebruikt wordt.

Start me up

We hebben ondertussen een bruikbare applicatie. Wees niet bang, run het; zwijmel in je eigen creatie.
Maar, hoor ik je zeggen, hoe kunnen we garanderen dat onze code stabiel is als we geen tests hebben?

Prepping to test

We hebben al de jQuery and KnockoutJS packages geïnstalleerd. Nu zijn Jasmine en Karma aan de beurt.

Jasmine is a behavior-driven development framework for testing JavaScript code

en Karma is een fantastische test-runner die jouw JavaSscript bestanden in de gaten houdt en, net als NCrunch voor .NET unit tests, jouw testen automatisch runt wanneer deze bestanden veranderen.

Om van de in VS2015 built-in npm support te profiteren installeren we deze keer de benodigde packages vanuit de npm repository.

Met de --save-dev aan het einde van beide commands zeggen we tegen npm dat deze packages alleen tijdens development nodig zijn.

Version mismatch

Het is je wellicht opgevallen dat we een specifieke versie van de karma-babel-preprocessor hebben geïnstalleerd.
Dit komt omdat, op het moment van schrijven, Babel 6 al uitgekomen is maar het wordt nog niet ondersteund door de huidige jspm (0.16.19) en SystemJs (0.19.9) versies. De laatste karma-babel-preprocessor versie is reeds op Babel 6 gebaseerd; version 5.2.2 is momenteel de laatste door jspm ondersteunde versie.

Babel 6 ondersteuning is gepland voor SystemJS 0.20.0, welke relatief snel uit zou moeten komen.

We moeten Karma nog configureren, maar laten we eerst een unit-test schrijven met Jasmine voor we verder gaan.

fullName.spec.js

Maak een nieuw bestand aan in the test folder. Ik heb het fullName.spec.js genoemd, maar het maakt helemaal niets uit hoe je het noemt.
De test wordt natuurlijk zo simpel als het viewmodel, maar vergeet niet dat het idee is om Jasmine and Karma aan de praat te krijgen, niet per se om fijne kneepjes van JavaScript unit testing te exploreren. Mijn testbestand ziet er als volgt uit:

Configuring Karma

Om Karma werkend te krijgen is nogal wat configuratie vereist. Gelukkig wordt een groot deel van het werk voor ons gedaan door een simpel commando te runnen en een paar vragen te beantwoorden.

Open nogmaals Command Prompt, ga naar de root van het project (één niveau hoger dan de wwwroot folder), en run:

Zoals gezegd zal Karma nu een aantal vragen stellen. Dit zijn de antwoorden:

Dit creëert een karma.conf.js bestand in de folder waar je het commando runt. We moeten nu dit configuratie bestand aanpassen om Karma te laten weten dat we een transpiler (BabelJs) willen gebruiken.

jspm

Vind in karma.conf.js waar de frameworks gedefinieerd zijn en voeg jspm toe aan de array.

Nu moeten we jspm configureren. Voeg de volgende code toe aan de config file. Maakt niet uit waar, maar ik heb het direct onder frameworks toegevoegd:

De “jspm” sectie zegt tegen Karma waar de jspm packages en onze JavaScript bestanden te vinden zijn. De “proxies” sectie legt de link tussen de wwwroot en Karmas baseUrl zodat Karma de packages kan vinden. Dit is de reden waarom we de baseUrl setting uit config.js verwijderd hebben: Het zou Karma in de war brengen.

Wijzig uiteraard de loadFiles sectie als je mijn voorgestelde folderstructuur niet gehanteerd hebt.

Babel preprocessor setup

Ok mensen, hou je vast want we zijn er bijna!
Indien we nu de tests met Karma zouden proberen te runnen zou Karma niet weten hoe ES2015 code te interpreteren. Karma heeft, net als de applicatie, een transpiler nodig.

Plaats de volgende code in karma.config.js; maakt wederom niet uit waar:

In de preprocessors sectie zeggen we tegen Karma dat BabelJs nodig is om onze JavaScript broncode en onze tests te pre-processen. In de babelPreprocessor sectie kunnen we Babel zelf configureren. We zouden kunnen aangeven, bijvoorbeeld, dat we optionele ES2016 features als class decorators willen gebruiken.

Time to see what the fuss is all about

Als je nooit Karma of een andere test-runner hebt gebruikt vraag je je wellicht af waarom je in vredesnaam zo veel moeite zou willen doen om het te configureren. Daarentegen, als je NCrunch gebruikt met Visual Studio weet je al wat je kunt verwachten en dat het lichtelijk briljant is.

Open, nogmaals, een Command Prompt en ga naar de root van het project, de folder waar karma.conf.js ligt, en run het volgende commando:

Karma zal een browser venster openen (een instantie van Chrome). Die kun je minimaliseren en negeren voorlopig; het wordt wel handig wanneer je falende tests wilt debuggen.

In de console kun je zien dat Karma 2 tests heeft gerund, en dat één daarvan faalt. Maar dat heb ik express gedaan, weet je nog?

We gaan nu de falende test laten slagen; prepare to be amazed!

Karma magic

Open fullName.spec.js en verander de regel

in:

Sla het bestand op terwijl je de console in de gaten houdt en… BAM! Karma heeft gezien dat één van onze bestanden veranderd is en heeft de testen opnieuw gerund.
Dit betekent dat je geen extra handelingen hoeft te verrichten om je tests te runnen terwijl je ontwikkelt. Karma voor de front-end en NCrunch voor de back-end en je weet onmiddellijk wanneer jouw code één van de bestaande tests breekt. Als je hierdoor geen glimlach op je gezicht krijgt…

Kan ik dit in het wild gebruiken?

Absoluut, maar de setup zoals het hier staat is niet hoe wij het in een productieomgeving gebruiken. Zoals het hier staat moet SystemJs Babel on the fly gebruiken om de transpilatie in de browser te doen. Zoals je je kunt voorstellen wordt de applicatie hier niet bepaald sneller van.

Hoe gebruiken wij het dan wel? Bijgaand onze setup, zonder teveel in detail te treden omdat het genoeg stof voor een nieuwe post zou zijn:

  1. De src folder met onze JavaScript bronbestanden (models, viewmodels, etc) leeft buiten wwwroot
  2. We hebben have gulp taken gedefinieerd om de *.js bestanden te transpileren wanneer de solution gebuild wordt.
  3. Andere gulp taken kopiëren de ge-transpileerde bestanden naar een dist folder in wwwroot.
  4. SystemJs gaat in de dist folder op zoek naar broncode bestanden. Deze zijn al ge-transpileerd.
  5. Een “watch” gulp task houdt de broncode bestanden in de gaten en zet het hele build-en-kopieer mechanisme op gang wanneer deze aangepast zijn.
  6. Karma zoekt nog steeds in src om te runnen. Het is niet erg dat deze on the fly gestranspileerd worden.

Dit is het dus. Ik hoop dat je er iets aan hebt! Mocht je nog vragen of op- en aanmerkingen hebben, plaats ze dan in de commentsectie hieronder!

Reacties (5)

  1. Denver schreef:

    Interessant artikel, Sergi! Web development zit sinds enige tijd in een behoorlijke stroomversnelling. Met name door package managers zoals jspm en npm. Een interessant artikel die ik daar onlangs over heb gelezen geeft daar een interessante (en kritische) kijk op. Het gebruik van package managers is super tof, maar nadenken over de kanttekeningen is geen verkeerd idee denk ik (https://medium.com/@wob/the-sad-state-of-web-development-1603a861d29f#.xt58gxny8)

    Keep up the good work! 🙂

    1. Sergi Papaseit schreef:

      Dank je! Deze mega-rant kwam ik ook tegen toen ik al bijna klaar was met deze blogpost. Mijn god wat ben was ik er mee eens op dat moment haha! Het idee van package managers, gulp tasks, etc i heel tof, maar ik heb steeds vaker het idee dat web development momenteel “broken” is. Dat denk ik weliswaar alleen wanneer een package update alles naar z’n grotje helpt, maar toch. In dezelfde trend: https://medium.com/@housecor/why-i-left-gulp-and-grunt-for-npm-scripts-3d6853dd22b8#.z7h0qd8mv

  2. Richard Nobel schreef:

    Goed artikel 🙂 In ’t front-end team bij Performation — waar ik terecht kwam via KPIT Recruitment 😀 — gebruiken we AngularJS. Ook met Jasmine tests maar dan als JavaScript test runner: “Chutzpah” (werkt tevens vanuit VS2015) i.p.v. Karma.

    Er zijn inderdaad continu veranderingen in de Web-wereld… frameworks, tools, nieuwe inzichten… Het wordt nooit saai, alleen soms wat onoverzichtelijk 😯

    💡 JavaScript draait zelfs op Embedded systemen (chips / mini computers), zie: http://www.espruino.com/
    Zo kan je inhaken op het “Internet of Things” zonder C of C++ te hoeven programmeren! Maar ook dat zou méér dan genoeg stof zijn voor een nieuw artikel :mrgreen:

    1. Sergi Papaseit schreef:

      Ook bedankt! 🙂

      Grappig dat je Chutzpah noemt, ik kwam het gisteren tegen. We zijn een AppVeyor VM aan het inrichten als build-server, en we willen daar alle front- en backend tests runnen voordat we (automagisch) naar Auzre deployen. Karma zal niet werken met AppVeyor, en zo zoekend kwamen we Chutzpah tegen. Weet je of het ook met BabelJs werkt? We gebruiken tegenwoordig allen ES2015 of ES2016 en we hebben een transpiler nodig.

      Wallaby test runner (http://wallabyjs.com/) ziet er ook heel goed uit, moet ik zeggen.

      En inderdaad, als je van leren en jezelf vernieuwen houdt is dit de juiste carrière! 😉

      1. Richard Nobel schreef:

        Of Chutzpah goed met BabelJS samen gaat, daar heb ik (nog) geen ervaring mee.

        Chutzpah heb ik eerder gebruikt met QUnit (test framework) + Sinon,JS (spy/stub/mock library). En nu dus met Angular-mock (ngMock) en Jasmine.

        Wellicht komt ’t er nog eens van om BabelJS te proberen. Waar ik ook nog naar wil kijken is: TypeScript http://www.typescriptlang.org/ .

        “Babel is a much safer option, since it follows the actual EcmaScript specifications, without additions (except the ability to enable experimental features). TypeScript on the other end adds additional stuff on top of EcmaScript official specs (e.g. Types and Generics to name a few) out of the box, which many could consider a good thing, but definitely out of the standard”.

        [ bron: http://www.ministryofprogramming.com/how-babel-compares-against-typescript/ ]

        Tja, zoveel interessante dingen, zo weinig tijd voor…

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *

Meer weten over onze werkwijze en dienstverlening? Neem gerust contact met ons op!