Modulaarisen JavaScriptin kirjoittaminen AMD:llä, CommonJS:llä ja ES Harmonylla

Tweet

Modulaarisuus Sovelluksen eriyttämisen merkitys

Kun sanomme, että sovellus on modulaarinen, tarkoitamme yleensä, että se koostuu joukosta moduuleihin varastoituja, erillisiä toiminnallisuuden osia. Kuten luultavasti tiedät, löyhä kytkentä helpottaa sovellusten ylläpidettävyyttä poistamalla riippuvuuksia mahdollisuuksien mukaan. Kun tämä on toteutettu tehokkaasti, on melko helppo nähdä, miten järjestelmän yhden osan muutokset voivat vaikuttaa toiseen osaan.

Toisin kuin joissakin perinteisemmissä ohjelmointikielissä, JavaScriptin nykyinen versio (ECMA-262) ei kuitenkaan tarjoa kehittäjille keinoja tuoda tällaisia koodimoduuleja siististi ja organisoidusti. Tämä on yksi niistä määrittelyihin liittyvistä huolenaiheista, jotka eivät ole vaatineet suurta pohdintaa ennen kuin vasta viime vuosina, jolloin järjestäytyneempien JavaScript-sovellusten tarve tuli ilmeiseksi.

Sen sijaan kehittäjät joutuvat tällä hetkellä turvautumaan moduuli- tai objekti-literaalimallien variaatioihin. Monissa näistä moduulikomentosarjat nivotaan yhteen DOM:ssa, ja nimiavaruudet kuvataan yhdellä globaalilla objektilla, jolloin arkkitehtuurissa on edelleen mahdollista syntyä nimikolareita. Riippuvuuksien hallintaan ei myöskään ole puhdasta tapaa ilman manuaalista työtä tai kolmannen osapuolen työkaluja.

Natiivit ratkaisut näihin ongelmiin tulevat ES Harmonyyn, mutta hyvä uutinen on se, että modulaarisen JavaScriptin kirjoittaminen ei ole koskaan ollut helpompaa, ja voit aloittaa sen jo tänään.

Tässä artikkelissa tarkastelemme kolmea modulaarisen JavaScriptin kirjoittamisen muotoa: AMD, CommonJS ja ehdotukset JavaScriptin seuraavaa versiota, Harmonya, varten.

Prelude A Note On Script Loaders

On vaikea keskustella AMD:n ja CommonJS:n moduuleista puhumatta huoneen elefantista – skriptinlataajista. Tällä hetkellä skriptilataus on keino päästä päämäärään, joka on modulaarinen JavaScript, jota voidaan käyttää nykyisissä sovelluksissa – tätä varten yhteensopivan skriptilataajan käyttö on valitettavasti välttämätöntä. Jotta tästä artikkelista saisi parhaan hyödyn irti, suosittelen hankkimaan perusymmärryksen siitä, miten suositut skriptilataustyökalut toimivat, jotta moduuliformaattien selitykset ovat järkeviä asiayhteydessään.

Moduulien lataamiseen AMD- ja CJS-formaateissa on useita loistavia moduulinlataajia, mutta henkilökohtaiset suosikkini ovat RequireJS ja curl.js. Täydelliset opetusohjelmat näistä työkaluista eivät kuulu tämän artikkelin piiriin, mutta voin suositella lukemaan John Hannin postauksen curl.js:stä ja James Burken RequireJS:n API-dokumentaation.

Tuotannon näkökulmasta optimointityökalujen (kuten RequireJS:n optimointityökalun) käyttöä skriptien ketjuttamiseen suositellaan käyttöönottoa varten, kun työskennellään tällaisten moduulien kanssa. Mielenkiintoista on, että Almond AMD:n shimin avulla RequireJS:ää ei tarvitse rullata käyttöönotetussa sivustossa ja se, mitä voisi pitää skriptien lataajana, voidaan helposti siirtää kehityksen ulkopuolelle.

Tämän sanottuaan James Burke luultavasti sanoisi, että skriptien dynaamisella lataamisella sivulatauksen jälkeen on edelleen käyttötapauksensa ja RequireJS voi auttaa myös tässä. Nämä huomiot mielessä, aloitetaanpa.

AMD A Format For Writing Modular JavaScript In The Browser

Yleinen tavoite AMD-formaatille (Asynchronous Module Definition) on tarjota modulaariselle JavaScriptille ratkaisu, jota kehittäjät voivat käyttää tänään. Se syntyi Dojon reaalimaailman kokemuksista XHR+evalin käytöstä, ja tämän formaatin kannattajat halusivat välttää sitä, että tulevat ratkaisut kärsisivät aiempien ratkaisujen heikkouksista.

Itse AMD-moduuliformaatti on ehdotus moduulien määrittelyyn, jossa sekä moduuli että riippuvuudet voidaan ladata asynkronisesti. Sillä on useita selviä etuja, kuten se, että se on luonteeltaan sekä asynkroninen että erittäin joustava, mikä poistaa koodin ja moduulin identiteetin välillä yleisesti esiintyvän tiukan kytkennän. Monet kehittäjät käyttävät sitä mielellään, ja sitä voidaan pitää luotettavana ponnahduslautana kohti ES Harmonyyn ehdotettua moduulijärjestelmää.

AMD alkoi moduuliformaatin määrittelyluonnoksena CommonJS-listalla, mutta koska se ei päässyt täyteen yhteisymmärrykseen, formaatin jatkokehitys siirtyi amdjs-ryhmään.

Tänään se on omaksuttu projekteissa kuten Dojo (1.7), MooTools (2.0), Firebug (1.8) ja jopa jQuery (1.7). Vaikka termi CommonJS AMD -formaatti on joskus nähty luonnossa, on parasta viitata siihen vain nimellä AMD tai Async Module support, koska kaikki CJS-listan osallistujat eivät halunneet jatkaa sitä.

Huomautus: Oli aika, jolloin ehdotusta kutsuttiin nimellä Modules Transport/C, mutta koska speksejä ei ollut suunnattu olemassa olevien CJS-moduulien kuljettamiseen vaan pikemminkin moduulien määrittelyyn, oli järkevämpää valita AMD-nimitys.

Alkuun pääseminen moduulien kanssa

Kaksi keskeistä käsitettä, jotka sinun täytyy tiedostaa tässä, ovat ajatus define-menetelmästä moduulien määrittelyn helpottamiseksi ja require-menetelmästä riippuvuuksien lataamisen käsittelemiseksi. define-menetelmää käytetään nimettyjen tai nimeämättömien moduulien määrittelyyn ehdotuksen perusteella käyttäen seuraavaa allekirjoitusta:

Kuten rivikommenteista voi päätellä, module_id on valinnainen argumentti, jota tarvitaan tyypillisesti vain silloin, kun käytetään muita kuin AMD:n konkatenaatiotyökaluja (saattaa olla muitakin ääritapauksia, joissa se on hyödyllinen). Kun tämä argumentti jätetään pois, kutsumme moduulia anonyymiksi.

Työskenneltäessä anonyymien moduulien kanssa ajatus moduulin identiteetistä on DRY, mikä tekee tiedostonimien ja koodin päällekkäisyyden välttämisestä triviaalia. Koska koodi on helpommin siirrettävissä, se voidaan helposti siirtää muihin paikkoihin (tai ympäri tiedostojärjestelmää) ilman, että itse koodia tarvitsee muuttaa tai sen tunnusta muuttaa. module_id vastaa kansiopolkuja yksinkertaisissa paketeissa ja silloin, kun niitä ei käytetä paketeissa. Kehittäjät voivat myös ajaa samaa koodia useissa ympäristöissä vain käyttämällä AMD:n optimoijaa, joka toimii CommonJS-ympäristön, kuten r.js:n, kanssa.

Takaisin define-allekirjoitukseen, dependencies-argumentti edustaa joukkoa riippuvuuksia, joita määrittelemäsi moduuli tarvitsee, ja kolmas argumentti (’definition function’) on funktio, joka suoritetaan moduulisi instantioimiseksi. Barebone-moduuli voitaisiin määritellä seuraavasti:

Understanding AMD: define()

require taas käytetään tyypillisesti koodin lataamiseen ylimmän tason JavaScript-tiedostossa tai moduulin sisällä, jos halutaan dynaamisesti hakea riippuvuuksia. Esimerkki sen käytöstä on:

Understanding AMD: require()

Dynaamisesti ladattavat riippuvuudet

Understanding AMD: plugins

Seuraava on esimerkki AMD-yhteensopivan pluginin määrittelystä:

Huomaa: Vaikka css! on sisällytetty CSS-riippuvuuksien lataamiseen yllä olevassa esimerkissä, on tärkeää muistaa, että tähän lähestymistapaan liittyy joitakin varoituksia, kuten se, että ei ole täysin mahdollista määrittää, milloin CSS on ladattu kokonaan. Riippuen siitä, miten lähestyt rakentamista, se voi myös johtaa siihen, että CSS sisällytetään riippuvuutena optimoituun tiedostoon, joten käytä CSS:ää ladattuna riippuvuutena tällaisissa tapauksissa varoen.

AMD-moduulien lataaminen require:n avulla.js

Loading AMD Modules Using curl.js

Modules With Deferred Dependencies

Why Is AMD A Better Choice For Writing Modular JavaScript?

  • Tarjoaa selkeän ehdotuksen siitä, miten joustavien moduulien määrittelyä tulisi lähestyä.
  • Merkittävästi puhtaampi kuin nykyiset globaalin nimiavaruuden ja <script>-tagien ratkaisut, joihin monet meistä luottavat. On siisti tapa ilmoittaa itsenäiset moduulit ja niiden mahdolliset riippuvuudet.
  • Moduulien määritelmät on kapseloitu, mikä auttaa meitä välttämään globaalin nimiavaruuden saastuttamisen.
  • Toimii paremmin kuin jotkin vaihtoehtoiset ratkaisut (esim. CommonJS, jota tarkastelemme pian). Ei ole ongelmia cross-domain-, local- tai debuggausongelmien kanssa, eikä ole riippuvainen palvelinpuolen työkalujen käytöstä. Useimmat AMD:n lataajat tukevat moduulien lataamista selaimessa ilman rakentamisprosessia.
  • Tarjoaa ”kuljetus”-lähestymistavan useiden moduulien sisällyttämiseksi yhteen tiedostoon. Muissa lähestymistavoissa, kuten CommonJS:ssä, ei ole vielä sovittu kuljetusmuodosta.
  • Skriptien laiska lataaminen on mahdollista, jos se on tarpeen.

AMD-moduulit Dojolla

AMD-yhteensopivien moduulien määrittely Dojon avulla on melko suoraviivaista. Kuten edellä, määrittele kaikki moduulien riippuvuudet matriisiin ensimmäisenä argumenttina ja anna callback (factory), joka suorittaa moduulin, kun riippuvuudet on ladattu. esim.g:

define(, function( Tooltip ){ //Our dijit tooltip is now available for local use new Tooltip(...);});

Huomaa moduulin anonyymi luonne, joka voidaan nyt sekä kuluttaa Dojon asynkronisella lataajalla, RequireJS:llä tai tavallisella dojo.require()-moduulinlataajalla, jota olet ehkä tottunut käyttämään.

Moduulien viittaamista ihmetteleville on tässä yhteydessä muutama mielenkiintoinen gotchas, jotka on hyvä tietää. Vaikka AMD:n suosima tapa viitata moduuleihin ilmoittaa ne riippuvuusluettelossa yhteensopivien argumenttien kanssa, Dojo 1.6:n build-järjestelmä ei tue tätä – se toimii oikeastaan vain AMD-yhteensopivissa loadereissa. e.g:

define(, function( cookie, Tooltip ){ var cookieValue = cookie("cookieName"); new Tree(...); });

Tässä on monia edistysaskeleita verrattuna sisäkkäisiin nimiavaruuksiin, koska moduulien ei enää tarvitse viitata suoraan täydellisiin nimiavaruuksiin joka kerta – tarvitsemme vain ’dojo/cookie’-polun riippuvuussuhteissa, ja kun se on alias-argumentti, siihen voidaan viitata kyseisellä muuttujalla. Tämä poistaa tarpeen kirjoittaa toistuvasti ’dojo.’ sovelluksissasi.

Huomautus: Vaikka Dojo 1.6 ei virallisesti tue käyttäjälähtöisiä AMD-moduuleja (eikä asynkronista latausta), on mahdollista saada tämä toimimaan Dojon kanssa käyttämällä useita eri skriptilataajia. Tällä hetkellä kaikki Dojon ydin- ja Dijit-moduulit on muutettu AMD-syntaksin mukaisiksi, ja parannettu yleinen AMD-tuki laskeutuu todennäköisesti 1.7:n ja 2.0:n väliin.

Viimeinen hankaluus, josta on syytä olla tietoinen, on se, että jos haluat jatkaa Dojon rakentamisjärjestelmän käyttämistä tai haluat siirtää vanhempia moduuleja tähän uudempaan AMD-tyyliin, seuraava sanallisempi versio mahdollistaa helpomman siirtymisen. Huomaa, että dojo ja dijit ja viitataan myös riippuvuuksina:

AMD-moduulien suunnittelumallit (Dojo)

Jos olet seurannut aiempia kirjoituksiani suunnittelumallien hyödyistä, tiedät, että ne voivat olla erittäin tehokkaita parantamaan tapaa, jolla lähestymme ratkaisujen strukturointia tavallisiin kehitysongelmiin. John Hann piti hiljattain erinomaisen esityksen AMD:n moduulien suunnittelumalleista, joissa käsiteltiin Singletonia, Decoratoria, Mediatoria ja muita. Suosittelen lämpimästi tutustumaan hänen dioihinsa, jos sinulla on tilaisuus.

Joitakin esimerkkejä näistä malleista on alla:

Decorator-malli:

Adapter-kuvio

AMD Modules With jQuery

The Basics

Toisin kuin Dojo, jQueryn mukana tulee oikeastaan vain yksi tiedosto, mutta ottaen huomioon kirjaston plugin-pohjaisen luonteen, voimme kuitenkin demonstroida alla, kuinka suoraviivaista on määritellä AMD-moduuli, joka käyttää sitä.

Tästä esimerkistä kuitenkin puuttuu jotain, ja se on rekisteröinnin käsite.

JQueryn rekisteröinti asynkronisena moduulina

Yksi tärkeimmistä ominaisuuksista, joka laskeutui jQuery 1.7:ään, oli tuki jQueryn rekisteröinnille asynkronisena moduulina. On olemassa useita yhteensopivia skriptinlataajia (kuten RequireJS ja curl), jotka pystyvät lataamaan moduuleja käyttäen asynkronista moduulimuotoa, ja tämä tarkoittaa, että tarvitaan vähemmän hakkerointeja, jotta asiat saadaan toimimaan.

JQueryn suosion vuoksi AMD:n lataajien on otettava huomioon, että kirjastosta ladataan useita versioita samalle sivulle, sillä ideaalitilanteessa ei haluta, että useat eri versiot latautuvat samaan aikaan. Lataajilla on mahdollisuus joko ottaa tämä asia erityisesti huomioon tai ohjeistaa käyttäjiään siitä, että kolmansien osapuolten skripteihin ja niiden kirjastoihin liittyy tunnettuja ongelmia.

Mitä 1.7-lisäys tuo mukanaan, on se, että se auttaa välttämään ongelmat, joissa sivun muu kolmannen osapuolen koodi lataa sivulle vahingossa jQueryn version, jota omistaja ei odottanut. Et halua, että muut instanssit sotkevat omasi, joten tästä voi olla hyötyä.

Tapa, jolla tämä toimii, on se, että käytettävä skriptinlataaja ilmaisee tukevansa useita jQuery-versioita määrittelemällä, että ominaisuus define.amd.jQuery on yhtä suuri kuin true. Niille, jotka ovat kiinnostuneita tarkemmista toteutuksen yksityiskohdista, ilmoitamme, että rekisteröimme jQueryn nimettynä moduulina, koska on olemassa vaara, että se voidaan ketjuttaa muiden tiedostojen kanssa, jotka saattavat käyttää AMD:n define()-menetelmää, mutta eivät käytä asianmukaista ketjuuntamisskriptiä, joka ymmärtää nimettömiä AMD:n moduulimäärityksiä.

Nimetty AMD tarjoaa turvaverkon, joka on sekä vankka että turvallinen useimmissa käyttötapauksissa.

Smarter jQuery Plugins

Olen hiljattain keskustellut täällä joistakin ideoista ja esimerkeistä siitä, miten jQuery-plugineja voitaisiin kirjoittaa käyttäen Universal Module Definition (UMD) -malleja. UMD:t määrittelevät moduulit, jotka voivat toimia sekä asiakkaalla että palvelimella sekä kaikkien tällä hetkellä saatavilla olevien suosittujen skriptinlataajien kanssa. Vaikka tämä on vielä uusi alue, jossa monet käsitteet ovat vielä viimeistelyvaiheessa, voit vapaasti katsoa koodinäytteitä jakson otsikossa AMD && CommonJS alla ja kertoa minulle, jos sinusta tuntuu, että voisimme tehdä jotain paremmin.

Mitkä skriptinlataajat & Frameworks tukevat AMD:tä?

selaimessa:
palvelimen puolella:
  • RequireJS http://requirejs.org
  • PINF http://github.com/pinf/loader-js

AMD Johtopäätökset

Ylläolevat esimerkit ovat hyvin triviaaleja esimerkkejä siitä, kuinka hyödyllisiä AMD-moduulit todella voivat olla, mutta toivottavasti ne luovat pohjan niiden toiminnan ymmärtämiselle.

Voi olla kiinnostavaa tietää, että monet näkyvät suuret sovellukset ja yritykset käyttävät tällä hetkellä AMD:n moduuleja osana arkkitehtuuriaan. Näihin kuuluvat muun muassa IBM ja BBC iPlayer, mikä korostaa sitä, miten vakavasti kehittäjät ottavat tämän formaatin huomioon yritystasolla.

Muutamia syitä siihen, miksi monet kehittäjät valitsevat AMD-moduulien käytön sovelluksissaan, saattaisit lukea tämän James Burken kirjoituksen.

CommonJS Palvelimelle optimoitu moduuliformaatti

CommonJS on vapaaehtoinen työryhmä, jonka tavoitteena on suunnitella, prototyypittää ja standardisoida JavaScriptin API:t. Tähän mennessä he ovat yrittäneet ratifioida standardeja sekä moduuleille että paketeille. CommonJS:n moduuliehdotus määrittelee yksinkertaisen API:n moduulien julistamiseen palvelinpuolella, ja toisin kuin AMD yrittää kattaa laajemman joukon huolenaiheita, kuten io:n, tiedostojärjestelmän, lupaukset ja muut.

Alkuun pääseminen

Rakenteen kannalta CJS-moduuli on uudelleenkäytettävä JavaScript-kappale, joka vie tiettyjä objekteja, jotka ovat riippuvaisen koodin käytettävissä – tällaisten moduulien ympärillä ei tyypillisesti ole funktiokääreitä (joten esimerkiksi define:aa ei käytetä tässä).

Korkealla tasolla ne sisältävät periaatteessa kaksi pääasiallista osaa: vapaan muuttujan nimeltä exports, joka sisältää ne objektit, jotka moduuli haluaa asettaa muiden moduulien saataville, ja require-funktion, jota moduulit voivat käyttää tuodakseen muiden moduulien vientituotteet.

CJS:n ymmärtäminen: require() ja vienti

Vientien peruskulutus

AMD-ekvivalentti ensimmäisestä CJS-esimerkistä

Monien riippuvuuksien kuluttaminen

app.js
bar.js
exports.name = 'bar';
foo.js
require('./bar');exports.helloWorld = function(){ return 'Hello World!!''}

Mitkä lataajat & kehykset tukevat CJS:ää?

selaimessa:
  • curl.js http://github.com/unscriptable/curl
  • SproutCore 1.1 http://sproutcore.com
  • PINF http://github.com/pinf/loader-js
  • (and more)
Server-puolella:

Soveltuuko CJS selaimeen?

On kehittäjiä, joiden mielestä CommonJS soveltuu paremmin palvelinpuolen kehitykseen, mikä on yksi syy siihen, että tällä hetkellä vallitsee erimielisyyttä siitä, mitä formaattia pitäisi käyttää ja tullaan käyttämään de facto-standardina ennen Harmonyn aikakautta jatkossa. Joihinkin CJS:ää vastustaviin argumentteihin kuuluu huomautus siitä, että monet CommonJS:n API:t käsittelevät palvelinorientoituneita ominaisuuksia, joita ei yksinkertaisesti pystyisi toteuttamaan selaintasolla JavaScriptillä – esimerkiksi io, system ja js voitaisiin niiden toiminnallisuuden luonteen vuoksi pitää toteuttamiskelvottomina.

Sitä huolimatta on hyödyllistä tietää, miten CJS:n moduulit jäsennetään, jotta pystymme paremmin arvioimaan, miten ne sopivat määriteltäessä moduuleja, joita voidaan käyttää kaikkialla. Moduuleja, joilla on sovelluksia sekä asiakkaalla että palvelimella, ovat esimerkiksi validointi-, muunto- ja templating-moottorit. Jotkut kehittäjät lähestyvät käyttämänsä formaatin valintaa siten, että he valitsevat CJS:n, jos moduulia voidaan käyttää palvelinpuolen ympäristössä, ja käyttävät AMD:tä, jos näin ei ole.

Koska AMD-moduulit pystyvät käyttämään liitännäisiä ja voivat määritellä yksityiskohtaisempia asioita, kuten konstruktoreita ja funktioita, tämä on järkevää. CJS-moduulit pystyvät määrittelemään vain objekteja, joiden kanssa voi olla työlästä työskennellä, jos yrität saada niistä konstruktoreita.

Vaikka se ei kuulu tähän artikkeliin, olet ehkä myös huomannut, että AMD:stä ja CJS:stä keskusteltaessa mainittiin erityyppisiä ’require’-menetelmiä.

Huoli samankaltaisesta nimeämiskäytännöstä on tietysti sekaannus, ja yhteisö on tällä hetkellä jakautunut globaalin require-funktion eduista. John Hannin ehdotus tässä on, että sen sijaan, että sitä kutsuttaisiin ’require’-funktioksi, mikä luultavasti ei saavuttaisi tavoitetta informoida käyttäjiä globaalin ja sisäisen require:n välisestä erosta, voisi olla järkevämpää nimetä globaali latausmetodi uudelleen joksikin muuksi (esim. kirjaston nimi). Tästä syystä curl.js:n kaltainen lataaja käyttää curl() eikä require.

AMD && CommonJS Kilpailevat, mutta yhtä pätevät standardit

Vaikka tässä artikkelissa on painotettu enemmän AMD:n käyttöä CJS:n sijaan, todellisuudessa molemmat formaatit ovat päteviä ja niille on käyttöä.

AMD omaksuu selain ensin -lähestymistavan kehitykseen, valitsee asynkronisen käyttäytymisen ja yksinkertaistetun taaksepäin yhteensopivuuden, mutta sillä ei ole mitään käsitystä File I/O:sta. Se tukee objekteja, funktioita, konstruktoreita, merkkijonoja, JSONia ja monia muita moduulityyppejä, jotka toimivat natiivisti selaimessa. Se on uskomattoman joustava.

CommonJS taas ottaa palvelin ensin -lähestymistavan, olettaen synkronisen käyttäytymisen, ei mitään globaalia taakkaa, kuten John Hann sanoisi, ja se yrittää huolehtia tulevaisuudesta (palvelimella). Tarkoitamme tällä sitä, että koska CJS tukee käärimättömiä moduuleja, se voi tuntua hieman läheisemmältä ES.next/Harmony-määrittelyihin nähden ja vapauttaa sinut AMD:n pakottamasta define()-kääreestä. CJS-moduulit tukevat kuitenkin vain objekteja moduuleina.

Vaikka ajatus vielä toisesta moduuliformaatista voi olla pelottava, saatat olla kiinnostunut joistakin näytteistä, jotka on tehty hybridi AMD/CJS- ja Univeral AMD/CJS-moduuleista.

Basic AMD Hybrid Format (John Hann)

AMD/CommonJS Universal Module Definition (Variation 2, UMDjs)

Extensible UMD Plugins With (Variation by myself and Thomas Davis).

core.js
myExtension.js
;(function ( name, definition ) { var theModule = definition(), hasDefine = typeof define === 'function', hasExports = typeof module !== 'undefined' && module.exports; if ( hasDefine ) { // AMD Module define(theModule); } else if ( hasExports ) { // Node.js Module module.exports = theModule; } else { // Assign to common namespaces or simply the global object (window) // account for for flat-file/global module extensions var obj = null; var namespaces = name.split("."); var scope = (this.jQuery || this.ender || this.$ || this); for (var i = 0; i 
app.js
$(function(){ // the plugin 'core' is exposed under a core namespace in // this example which we first cache var core = $.core; // use then use some of the built-in core functionality to // highlight all divs in the page yellow core.highlightAll(); // access the plugins (extensions) loaded into the 'plugin' // namespace of our core module: // Set the first div in the page to have a green background. core.plugin.setGreen("div:first"); // Here we're making use of the core's 'highlight' method // under the hood from a plugin loaded in after it // Set the last div to the 'errorColor' property defined in // our core module/plugin. If you review the code further down // you'll see how easy it is to consume properties and methods // between the core and other plugins core.plugin.setRed('div:last');});

ES Harmony Modules Of The Future

TC39, standardointielin, jonka tehtävänä on määritellä ECMAScriptin ja sen tulevien iteraatioiden syntaksi ja semantiikka, koostuu joukosta erittäin älykkäitä kehittäjiä. Jotkut näistä kehittäjistä (kuten Alex Russell) ovat seuranneet tarkkaan JavaScriptin käytön kehittymistä laajamittaisessa kehityksessä viime vuosina ja ovat erittäin tietoisia siitä, että tarvitaan parempia kieliominaisuuksia modulaarisemman JS:n kirjoittamiseen.

Sen vuoksi kieleen ehdotetaan tällä hetkellä useita jännittäviä lisäyksiä, kuten joustavia moduuleja, jotka voivat toimia sekä asiakkaalla että palvelimella, moduulinlataajaa ja paljon muuta. Tässä osiossa näytän joitakin koodinäytteitä ES.nextin moduulien syntaksista, jotta saat esimakua tulevasta.

Huomautus: Vaikka Harmony on vielä ehdotusvaiheessa, voit jo nyt kokeilla ES.nextin (osittaisia) ominaisuuksia, jotka käsittelevät natiivia tukea modulaarisen JavaScriptin kirjoittamiseen Googlen Traceur-kääntäjän ansiosta. Jos haluat päästä käyttämään Traceuria alle minuutissa, lue tämä aloitusopas. Siitä on myös JSConf-esitys, joka kannattaa katsoa, jos olet kiinnostunut oppimaan lisää projektista.

Moduulit, joissa on tuontia ja vientiä

Jos olet lukenut läpi AMD- ja CJS-moduuleja käsittelevät osiot, saatat tuntea moduulien riippuvuuksien (tuontia) ja moduulien viennin (tai julkiset API:t/muuttujat, joita annamme muiden moduulien käyttää) käsitteet. ES.nextissa näitä käsitteitä on ehdotettu hieman ytimekkäämmin siten, että riippuvuudet määritellään import-avainsanalla. export ei poikkea suuresti siitä, mitä voisimme odottaa, ja uskon, että monet kehittäjät katsovat alla olevaa koodia ja "tajuavat" sen heti.

  • import-ilmoitukset sitovat moduulin viennit paikallisiksi muuttujiksi, ja ne voidaan nimetä uudelleen nimikollisionien/konfliktien välttämiseksi.
  • export-ilmoitukset ilmoittavat, että moduulin paikallinen sitominen on ulkoisesti näkyvissä siten, että muut moduulit voivat lukea viennit, mutta eivät voi muuttaa niitä. Mielenkiintoista on, että moduulit voivat viedä lapsimoduuleja, mutta eivät voi viedä muualla määriteltyjä moduuleja. Voit myös nimetä viennit uudelleen niin, että niiden ulkoinen nimi eroaa niiden paikallisesta nimestä.

Moduulit, jotka on ladattu etälähteistä

Moduuliehdotukset palvelevat myös moduuleja, jotka ovat etälähteissä (esim. kolmannen osapuolen API-kääre), mikä tekee moduulien lataamisesta ulkoisista sijainneista yksinkertaista. Tässä on esimerkki, jossa vedämme edellä määrittelemämme moduulin sisään ja hyödynnämme sitä:

Moduulinlataajan API

Moduulinlataajaehdotus kuvaa dynaamisen API:n moduulien lataamiseen hyvin kontrolloiduissa yhteyksissä. Lataajan tukemia allekirjoituksia ovat load( url, moduleInstance, error) moduulien lataamiseen, createModule( object, globalModuleReferences) ja muut. Tässä on toinen esimerkki siitä, että lataamme dynaamisesti alun perin määrittelemämme moduulin. Huomaa, että toisin kuin edellisessä esimerkissä, jossa vedimme moduulin sisään etälähteestä, moduulinlataajan API soveltuu paremmin dynaamisiin konteksteihin.

Loader.load('http://addyosmani.com/factory/cakes.js', function(cakeFactory){ cakeFactory.oven.makeCupcake('chocolate'); });

CommonJS:n kaltaiset moduulit palvelimelle

Palvelinorientoituneille kehittäjille ES.nextiin ehdotettu moduulijärjestelmä ei rajoitu vain moduulien tarkasteluun selaimessa. Alla esimerkkeinä CJS:n kaltainen moduuli, jota ehdotetaan käytettäväksi palvelimella:

// io/File.jsexport function open(path) { ... };export function close(hnd) { ... };
module lexer from 'compiler/LexicalHandler';module stdlib from '@std'; //... scan(cmdline) ...

Classes With Constructors, Getters & Setters

Luokan käsite on aina ollut kiistanalainen asia puristien kanssa, ja tähän asti olemme tulleet toimeen joko turvautumalla JavaScriptin prototyyppiseen luonteeseen tai käyttämällä kehyksiä tai abstraktioita, jotka tarjoavat mahdollisuuden käyttää luokkamäärittelyjä sellaisessa muodossa, että ne desugaroituvat samaan prototyyppiseen käyttäytymiseen.

Harmoniassa luokat tulevat osana kieltä yhdessä konstruktorien ja (vihdoin) jonkinlaisen todellisen yksityisyyden tunteen kanssa. Seuraavissa esimerkeissä olen lisännyt joitain rivikommentteja auttaakseni sinua ymmärtämään, miten luokat rakentuvat, mutta saatat myös huomata sanan ’funktio’ puuttumisen täältä. Tämä ei ole kirjoitusvirhe: TC39 on pyrkinyt tietoisesti vähentämään function-avainsanan väärinkäyttöä kaikessa, ja toivomme, että tämä auttaa yksinkertaistamaan koodin kirjoittamista.

class Cake{ // We can define the body of a class' constructor // function by using the keyword 'constructor' followed // by an argument list of public and private declarations. constructor( name, toppings, price, cakeSize ){ public name = name; public cakeSize = cakeSize; public toppings = toppings; private price = price; } // As a part of ES.next's efforts to decrease the unnecessary // use of 'function' for everything, you'll notice that it's // dropped for cases such as the following. Here an identifier // followed by an argument list and a body defines a new method addTopping( topping ){ public(this).toppings.push(topping); } // Getters can be defined by declaring get before // an identifier/method name and a curly body. get allToppings(){ return public(this).toppings; } get qualifiesForDiscount(){ return private(this).price > 5; } // Similar to getters, setters can be defined by using // the 'set' keyword before an identifier set cakeSize( cSize ){ if( cSize 

ES Harmony Conclusions

As you can see, ES.next is coming with some exciting new additions. Although Traceur can be used to an extent to try our such features in the present, remember that it may not be the best idea to plan out your system to use Harmony (just yet). There are risks here such as specifications changing and a potential failure at the cross-browser level (IE9 for example will take a while to die) so your best bets until we have both spec finalization and coverage are AMD (for in-browser modules) and CJS (for those on the server).

Conclusions And Further Reading A Review

In this article we've reviewed several of the options available for writing modular JavaScript using modern module formats. These formats have a number of advantages over using the (classical) module pattern alone including: avoiding a need for developers to create global variables for each module they create, better support for static and dynamic dependency management, improved compatibility with script loaders, better (optional) compatibility for modules on the server and more.

In short, I recommend trying out what's been suggested today as these formats offer a lot of power and flexibility that can help when building applications based on many reusable blocks of functionality.

And that's it for now. If you have further questions about any of the topics covered today, feel free to hit me up on twitter and I'll do my best to help!

Copyright Addy Osmani, 2012. All Rights Reserved.

class Cake{ // We can define the body of a class' constructor // function by using the keyword 'constructor' followed // by an argument list of public and private declarations. constructor( name, toppings, price, cakeSize ){ public name = name; public cakeSize = cakeSize; public toppings = toppings; private price = price; } // As a part of ES.next's efforts to decrease the unnecessary // use of 'function' for everything, you'll notice that it's // dropped for cases such as the following. Here an identifier // followed by an argument list and a body defines a new method addTopping( topping ){ public(this).toppings.push(topping); } // Getters can be defined by declaring get before // an identifier/method name and a curly body. get allToppings(){ return public(this).toppings; } get qualifiesForDiscount(){ return private(this).price > 5; } // Similar to getters, setters can be defined by using // the 'set' keyword before an identifier set cakeSize( cSize ){ if( cSize 

ES Harmony Conclusions

As you can see, ES.next is coming with some exciting new additions. Although Traceur can be used to an extent to try our such features in the present, remember that it may not be the best idea to plan out your system to use Harmony (just yet). There are risks here such as specifications changing and a potential failure at the cross-browser level (IE9 for example will take a while to die) so your best bets until we have both spec finalization and coverage are AMD (for in-browser modules) and CJS (for those on the server).

Conclusions And Further Reading A Review

In this article we've reviewed several of the options available for writing modular JavaScript using modern module formats. These formats have a number of advantages over using the (classical) module pattern alone including: avoiding a need for developers to create global variables for each module they create, better support for static and dynamic dependency management, improved compatibility with script loaders, better (optional) compatibility for modules on the server and more.

In short, I recommend trying out what's been suggested today as these formats offer a lot of power and flexibility that can help when building applications based on many reusable blocks of functionality.

And that's it for now. If you have further questions about any of the topics covered today, feel free to hit me up on twitter and I'll do my best to help!

Copyright Addy Osmani, 2012. All Rights Reserved.

Vastaa

Sähköpostiosoitettasi ei julkaista.