Heeft u ooit gewenst dat u uw code aan de clientzijde leesbaar en, nog belangrijker, foutloos kunt houden, zelfs nadat u deze hebt gecombineerd en verkleind, zonder dat dit gevolgen heeft voor de prestaties? Welnu, dat kan nu via de magie van bronkaarten .
Bronkaarten zijn een manier om een gecombineerd/verkleind bestand terug te brengen naar een ongebouwde staat. Wanneer u voor productie bouwt, genereert u, naast het verkleinen en combineren van uw JavaScript-bestanden, een bronkaart met informatie over uw originele bestanden. Wanneer u een bepaald regel- en kolomnummer opvraagt in uw gegenereerde JavaScript, kunt u een zoekopdracht uitvoeren in de bronkaart die de oorspronkelijke locatie retourneert. Ontwikkelaarstools (momenteel WebKit nightly builds, Google Chrome of Firefox 23+) kunnen de bronkaart automatisch parseren en het laten lijken alsof u niet-verkleinde en niet-gecombineerde bestanden gebruikt.
Met de demo kunt u met de rechtermuisknop ergens in het tekstgebied klikken dat de gegenereerde bron bevat. Als u "Originele locatie ophalen" selecteert, wordt de bronkaart opgevraagd door het gegenereerde regel- en kolomnummer door te geven en de positie in de originele code terug te geven. Zorg ervoor dat uw console open is, zodat u de uitvoer kunt zien.
Echte wereld
Voordat u de volgende implementatie van Bronkaarten in de echte wereld bekijkt, moet u ervoor zorgen dat u de bronkaartenfunctie in Chrome Canary of WebKit elke avond hebt ingeschakeld door op het instellingenwieltje in het paneel met dev-tools te klikken en de optie "Bronkaarten inschakelen" aan te vinken.
In Firefox 23+ zijn bronkaarten standaard ingeschakeld in de ingebouwde ontwikkelaarstools.
Waarom zou ik mij zorgen maken over bronkaarten?
Op dit moment werkt source mapping alleen tussen ongecomprimeerd/gecombineerd JavaScript en gecomprimeerd/ongecombineerd JavaScript, maar de toekomst ziet er rooskleurig uit met gesprekken over gecompileerde naar JavaScript-talen zoals CoffeeScript en zelfs de mogelijkheid om ondersteuning toe te voegen voor CSS-preprocessors zoals SASS of LESS .
In de toekomst zouden we vrijwel elke taal gemakkelijk kunnen gebruiken alsof deze native in de browser wordt ondersteund met bronkaarten:
- KoffieScript
- ECMAScript 6 en hoger
- SASS/LESS en anderen
- Vrijwel elke taal die naar JavaScript compileert
Kijk eens naar deze screencast van CoffeeScript waarvan fouten worden opgespoord in een experimentele versie van de Firefox-console:
De Google Web Toolkit (GWT) heeft onlangs ondersteuning voor Source Maps toegevoegd. Ray Cromwell van het GWT-team heeft een geweldige screencast gemaakt waarin ondersteuning voor bronkaarten in actie wordt getoond.
Een ander voorbeeld dat ik heb samengesteld, maakt gebruik van de Traceur- bibliotheek van Google waarmee je ES6 (ECMAScript 6 of Next) kunt schrijven en deze kunt compileren naar ES3-compatibele code. De Traceur-compiler genereert ook een bronkaart. Bekijk deze demo van ES6-eigenschappen en -klassen die worden gebruikt alsof ze native in de browser worden ondersteund, dankzij de bronkaart.
Met het tekstgebied in de demo kunt u ook ES6 schrijven, dat direct wordt gecompileerd en een bronkaart plus de equivalente ES3-code genereert.
Demo: schrijf ES6, debug het, bekijk brontoewijzing in actie
Hoe werkt de bronkaart?
De enige JavaScript-compiler/minifier die op dit moment ondersteuning biedt voor het genereren van bronkaarten is de Closure-compiler. (Ik zal later uitleggen hoe je het moet gebruiken.) Zodra je je JavaScript hebt gecombineerd en verkleind, zal er daarnaast een bronkaartbestand bestaan.
Momenteel voegt de Closure-compiler aan het einde niet de speciale opmerking toe die nodig is om aan de ontwikkelaarstools van een browser aan te geven dat er een bronkaart beschikbaar is:
//# sourceMappingURL=/path/to/file.js.map
Hierdoor kunnen ontwikkelaarstools oproepen terugverwijzen naar hun locatie in originele bronbestanden. Voorheen was het commentaarpragma //@
maar vanwege enkele problemen daarmee en IE voorwaardelijke compilatiecommentaar werd besloten om het te veranderen in //#
. Momenteel ondersteunen Chrome Canary, WebKit Nightly en Firefox 24+ het nieuwe commentaarpragma. Deze syntaxiswijziging heeft ook invloed op sourceURL.
Als het idee van de rare opmerking je niet bevalt, kun je als alternatief een speciale header instellen op je gecompileerde JavaScript-bestand:
X-SourceMap: /path/to/file.js.map
Net als de opmerking zal dit uw bronkaartconsument vertellen waar hij moet zoeken naar de bronkaart die aan een JavaScript-bestand is gekoppeld. Deze header omzeilt ook het probleem van het verwijzen naar bronkaarten in talen die geen commentaar van één regel ondersteunen.
Het bronkaartbestand wordt alleen gedownload als u bronkaarten hebt ingeschakeld en uw ontwikkelaarstools geopend zijn. U moet ook uw originele bestanden uploaden, zodat de ontwikkelaarstools ernaar kunnen verwijzen en deze indien nodig kunnen weergeven.
Hoe genereer ik een bronkaart?
U moet de Closure-compiler gebruiken om een bronkaart voor uw JavaScript-bestanden te verkleinen, samen te voegen en te genereren. De opdracht is als volgt:
java -jar compiler.jar \
--js script.js \
--create_source_map ./script-min.js.map \
--source_map_format=V3 \
--js_output_file script-min.js
De twee belangrijke opdrachtvlaggen zijn --create_source_map
en --source_map_format
. Dit is nodig omdat de standaardversie V2 is en we alleen met V3 willen werken.
De anatomie van een bronkaart
Om een bronkaart beter te begrijpen, nemen we een klein voorbeeld van een bronkaartbestand dat zou worden gegenereerd door de Closure-compiler en duiken we in meer detail over hoe de sectie "toewijzingen" werkt. Het volgende voorbeeld is een kleine variatie op het V3-specificatievoorbeeld .
{
version : 3,
file: "out.js",
sourceRoot : "",
sources: ["foo.js", "bar.js"],
names: ["src", "maps", "are", "fun"],
mappings: "AAgBC,SAAQ,CAAEA"
}
Hierboven kun je zien dat een bronkaart een letterlijk object is dat veel sappige informatie bevat:
- Versienummer waarop de bronkaart is gebaseerd
- De bestandsnaam van de gegenereerde code (uw minifed/gecombineerde productiebestand)
- Met sourceRoot kunt u de bronnen vooraf laten gaan door een mapstructuur - dit is ook een ruimtebesparende techniek
- bronnen bevat alle bestandsnamen die zijn gecombineerd
- namen bevat alle variabelen-/methodenamen die in uw code voorkomen.
- Ten slotte is de eigenschap mappings waar de magie gebeurt met behulp van Base64 VLQ-waarden. De echte ruimtebesparing gebeurt hier.
Base64 VLQ en de bronkaart klein houden
Oorspronkelijk had de bronkaartspecificatie een zeer uitgebreide uitvoer van alle toewijzingen, wat ertoe leidde dat de bronkaart ongeveer 10 keer zo groot was als de gegenereerde code. Versie twee verminderde dat met ongeveer 50% en versie drie verminderde het opnieuw met nog eens 50%, dus voor een bestand van 133 kB krijg je een bronkaart van ~300 kB.
Dus hoe hebben ze de omvang verkleind terwijl ze toch de complexe mappings behouden?
VLQ (Variable Length Hoeveelheid) wordt gebruikt samen met het coderen van de waarde in een Base64-waarde. De mappings-eigenschap is een supergrote tekenreeks. Binnen deze tekenreeks staan puntkomma's (;) die een regelnummer in het gegenereerde bestand vertegenwoordigen. Binnen elke regel staan komma's (,) die elk segment binnen die regel vertegenwoordigen. Elk van deze segmenten is 1, 4 of 5 in velden met variabele lengte. Sommige kunnen langer lijken, maar deze bevatten vervolgbits. Elk segment bouwt voort op het vorige, waardoor de bestandsgrootte kleiner wordt omdat elke bit relatief is ten opzichte van de voorgaande segmenten.
Zoals hierboven vermeld, kan elk segment 1, 4 of 5 variabele lengtes hebben. Dit diagram wordt beschouwd als een variabele lengte van vier met één vervolgbit (g). We splitsen dit segment op en laten u zien hoe de bronkaart de oorspronkelijke locatie uitwerkt.
De hierboven getoonde waarden zijn puur de door Base64 gedecodeerde waarden. Er is wat meer verwerking nodig om hun werkelijke waarden te verkrijgen. Elk segment levert gewoonlijk vijf dingen op:
- Gegenereerde kolom
- Origineel bestand waarin dit verscheen
- Origineel regelnummer
- Originele kolom
- En, indien beschikbaar, originele naam
Niet elk segment heeft een naam, methodenaam of argument, dus segmenten wisselen tussen de vier en vijf variabele lengtes. De g-waarde in het bovenstaande segmentdiagram is een zogenaamde voortzettingsbit. Dit maakt verdere optimalisatie mogelijk in de Base64 VLQ-decoderingsfase. Met een vervolgbit kun je voortbouwen op een segmentwaarde, zodat je grote getallen kunt opslaan zonder dat je een groot getal hoeft op te slaan, een zeer slimme ruimtebesparende techniek die zijn oorsprong vindt in het midi-formaat.
Het bovenstaande diagram AAgBC
zou, zodra het verder is verwerkt, 0, 0, 32, 16, 1 retourneren - waarbij 32 de vervolgbit is die helpt bij het opbouwen van de volgende waarde van 16. B puur gedecodeerd in Base64 is 1. De belangrijke waarden die worden gebruikt zijn dus 0, 0, 16, 1. Dit laat ons dan weten dat regel 1 (regels worden bijgehouden tot aan de puntkomma's) kolom 0 van het gegenereerde bestand wordt toegewezen aan bestand 0 (array van bestanden 0 is foo.js), regel 16 in kolom 1.
Om te laten zien hoe de segmenten worden gedecodeerd, zal ik verwijzen naar Mozilla's Source Map JavaScript-bibliotheek . Je kunt ook de brontoewijzingscode van WebKit dev tools bekijken, ook geschreven in JavaScript.
Om goed te begrijpen hoe we de waarde 16 uit B halen, moeten we een basiskennis hebben van bitsgewijze operatoren en hoe de specificatie werkt voor brontoewijzing. Het voorgaande cijfer, g, wordt gemarkeerd als een vervolgbit door het cijfer (32) en de VLQ_CONTINUATION_BIT (binair 100000 of 32) te vergelijken met behulp van de bitsgewijze AND (&) operator.
32 & 32 = 32
// or
100000
|
|
V
100000
Dit retourneert een 1 in elke bitpositie waar deze beide verschijnen. Dus een in Base64 gedecodeerde waarde van 33 & 32
zou 32 retourneren, omdat ze alleen de 32-bits locatie delen, zoals je kunt zien in het bovenstaande diagram. Dit verhoogt vervolgens de bitverschuivingswaarde met 5 voor elk voorafgaand vervolgbit. In het bovenstaande geval wordt het slechts één keer met 5 verschoven, dus wordt 1 (B) met 5 verschoven.
1 <<../ 5 // 32
// Shift the bit by 5 spots
______
| |
V V
100001 = 100000 = 32
Die waarde wordt vervolgens omgezet van een met VLQ ondertekende waarde door het getal (32) één plek naar rechts te verschuiven.
32 >> 1 // 16
//or
100000
|
|
V
010000 = 16
Dus daar hebben we het: zo verander je 1 in 16. Dit lijkt misschien een ingewikkeld proces, maar zodra de getallen groter worden, wordt het logischer.
Mogelijke XSSI-problemen
De specificatie vermeldt problemen met de opname van cross-site scripts die kunnen voortvloeien uit het gebruik van een bronkaart. Om dit te verhelpen, wordt aanbevolen dat u de eerste regel van uw bronkaart voorafgaat met " )]}
" om JavaScript opzettelijk ongeldig te maken, zodat er een syntaxisfout optreedt. De WebKit-ontwikkeltools kunnen dit al aan.
if (response.slice(0, 3) === ")]}") {
response = response.substring(response.indexOf('\n'));
}
Zoals hierboven weergegeven, worden de eerste drie tekens in plakjes gesneden om te controleren of ze overeenkomen met de syntaxisfout in de specificatie. Als dat het geval is, worden alle tekens verwijderd die naar de eerste nieuwe regelentiteit leiden (\n).
sourceURL
and displayName
in action: Eval and anonymous functions
Hoewel ze geen deel uitmaken van de bronkaartspecificaties, kunt u met de volgende twee conventies de ontwikkeling veel eenvoudiger maken als u met evals en anonieme functies werkt.
De eerste helper lijkt erg op de //# sourceMappingURL
eigenschap en wordt feitelijk vermeld in de bronkaart V3-specificatie. Door de volgende speciale opmerking in uw code op te nemen, die wordt geëvalueerd, kunt u evaluaties een naam geven, zodat ze als logischere namen verschijnen in uw dev-tools. Bekijk een eenvoudige demo met behulp van de CoffeeScript-compiler:
Demo: zie eval()
'd-code als script weergeven via sourceURL
//# sourceURL=sqrt.coffee
Met de andere helper kunt u anonieme functies een naam geven met behulp van de eigenschap displayName
die beschikbaar is in de huidige context van de anonieme functie. Maak een profiel van de volgende demo om de eigenschap displayName
in actie te zien.
btns[0].addEventListener("click", function(e) {
var fn = function() {
console.log("You clicked button number: 1");
};
fn.displayName = "Anonymous function of button 1";
return fn();
}, false);
Wanneer u uw code profileert binnen de dev-tools, wordt de eigenschap displayName
weergegeven in plaats van iets als (anonymous)
. DisplayName is echter vrijwel dood in het water en zal niet in Chrome terechtkomen. Maar alle hoop is niet verloren en er is een veel beter voorstel voorgesteld, genaamd debugName .
Op het moment van schrijven is de evaluatienaam alleen beschikbaar in Firefox- en WebKit-browsers. De eigenschap displayName
is alleen aanwezig in WebKit nightlies.
Laten we samenkomen
Momenteel is er een zeer langdurige discussie over de toevoeging van bronkaartondersteuning aan CoffeeScript. Bekijk het probleem eens en voeg uw steun toe voor het toevoegen van bronkaarten aan de CoffeeScript-compiler. Dit zal een enorme overwinning zijn voor CoffeeScript en zijn toegewijde volgers.
UglifyJS heeft ook een probleem met de bronkaart waar u ook naar moet kijken.
Veel tools genereren bronkaarten, waaronder de coffeescript-compiler. Ik beschouw dit nu als een betwistbaar punt.
Hoe meer tools we tot onze beschikking hebben waarmee we bronkaarten kunnen genereren, hoe beter we af zijn. Vraag dus om ondersteuning voor bronkaarten of voeg ze toe aan je favoriete open source-project.
Het is niet perfect
Eén ding waar bronkaarten op dit moment niet in voorzien, zijn kijkuitdrukkingen. Het probleem is dat het proberen om een argument of variabelenaam te inspecteren binnen de huidige uitvoeringscontext niets oplevert, omdat het niet echt bestaat. Hiervoor zou een soort omgekeerde mapping nodig zijn om de echte naam van het argument/de variabele die u wilt inspecteren op te zoeken, vergeleken met de daadwerkelijke naam van het argument/de variabele in uw gecompileerde JavaScript.
Dit is natuurlijk een oplosbaar probleem en met meer aandacht voor bronkaarten kunnen we een aantal fantastische functies en betere stabiliteit gaan zien.
Problemen
Onlangs heeft jQuery 1.9 ondersteuning toegevoegd voor bronkaarten wanneer deze worden aangeboden via officiële CDN's. Het wees ook op een eigenaardige bug wanneer IE voorwaardelijke compilatieopmerkingen (//@cc_on) worden gebruikt voordat jQuery wordt geladen. Sindsdien is er een commitment geweest om dit te verzachten door de sourceMappingURL in een commentaar van meerdere regels te plaatsen. De les die we moeten leren: gebruik geen voorwaardelijk commentaar.
Dit is sindsdien verholpen door de syntaxis te wijzigen in //#
.
Hulpmiddelen en middelen
Hier zijn nog enkele bronnen en hulpmiddelen die u zou moeten raadplegen:
- Nick Fitzgerald heeft een fork van UglifyJS met ondersteuning voor bronkaarten
- Paul Irish heeft een handige kleine demo met bronkaarten
- Bekijk de WebKit-wijzigingenset van wanneer deze is uitgebracht
- De wijzigingenset bevatte ook een lay-outtest waarmee dit hele artikel op gang kwam
- Mozilla heeft een bug die u moet volgen met betrekking tot de status van bronkaarten in de ingebouwde console
- Conrad Irwin heeft een superhandig bronkaartjuweeltje geschreven voor al jullie Ruby-gebruikers
- Nog wat meer informatie over eval-naamgeving en de eigenschap displayName
- U kunt de Closure Compilers-bron bekijken voor het maken van bronkaarten
- Er zijn enkele schermafbeeldingen en er wordt gesproken over ondersteuning voor GWT-bronkaarten
Bronkaarten zijn een zeer krachtig hulpprogramma in de toolset van een ontwikkelaar. Het is superhandig om uw web-app overzichtelijk maar gemakkelijk te kunnen debuggen. Het is ook een zeer krachtig leermiddel voor nieuwere ontwikkelaars om te zien hoe ervaren ontwikkelaars hun apps structureren en schrijven zonder door onleesbare verkleinde code te hoeven waden.
Waar wacht je op? Begin nu met het genereren van bronkaarten voor alle projecten!