Vai al contenuto principaleVai al footer
bazarjs
|
02 febbraio 15

BazarJS: Preprocessori Javascript

Nuovo episodio della serie BazarJS, questa volta dedicato a Javascript: la sua storia, il suo passato e il suo futuro. Passando per i pre-processori CoffeeScript e TypeScript, inizieremo a conoscere ECMAScript 6 per accorgerci che, questa volta, possiamo inizare a prendere Javascript davvero sul serio.

Stefano VernaHead of DatoCMS

Javascript ha una lunga storia. Siamo nel 1995: in piena browser war tra Netscape Navigator 2.0 ed Internet Explorer 1.0, viene affidato a Brendan Eich il compito di realizzare un semplice linguaggio interpretato pensato per programmatori amatoriali, a complemento dell'offerta di Netscape che, per utilizzi più professionali su web, stava invece investendo tutto sulle applet Java. Citando lo stesso Eich:

Javascript had to "look like Java", only less so. It had to be Java's dumb kid brother or boy-hostage sidekick. Plus, I had to be done in ten days, or something worse than JS would have happened.

Su queste premesse, la domanda è lecita: come è stato possibile passare da anni di (meritate) derisioni sui suoi limiti ed idiosincrasie alle ultime statistiche pubblicate da RedMonk, che vedono Javascript come primo linguaggio in assoluto in termini di utilizzo globale?

Sicuramente non è stato un percorso lineare, e più di una volta si è tentato di toglierlo di mezzo, rimpiazzandolo con qualcosa di più "serio". Per fare un esempio concreto, i team dietro alla creazione delle prime web application client-side di successo della storia, Gmail e Google Maps, sicuramente non vedevano in Javascript un alleato su cui fare affidamento.

Gli strumenti nati in quegli anni — GWT, Cappuccino, Pyjamas — sono una chiara dimostrazione del fatto che tutto si voleva, tranne che scrivere applicazioni web complesse sfruttando Javascript. Molto meglio lavorare con paradigmi tipicamente desktop rispettivamente in Java, Objective-C o Python, delegando ad un tool la "compilazione" del risultato finale su tecnologie native web. Javascript-as-bytecode, per intenderci, e nulla di più.

CoffeeScript

Gli anni passano, e timidamente si inizia a dare fiducia al linguaggio, con approcci allo sviluppo su web sempre più "nativi" e slegati dalle vecchie logiche di sviluppo desktop.

Nel 2009, con la pubblicazione di CoffeeScript da parte di Jeremy Ashkenas, si assiste ufficialmente alla nascita e alla crescita di un nuovo approccio meno negativo e pessimista nei confronti di Javascript, ben descritto nell'incipit stesso del progetto. Cos'è CoffeScript?

CoffeeScript is a little language that compiles into JavaScript. Underneath that awkward Java-esque patina, JavaScript has always had a gorgeous heart. CoffeeScript is an attempt to expose the good parts of JavaScript in a simple way.

The golden rule of CoffeeScript is: "It's just JavaScript". The code compiles one-to-one into the equivalent JS, and there is no interpretation at runtime.

In altre parole? Javascript non è il miglior linguaggio della storia, non prendiamoci in giro... ma se consideriamo ECMAScript 5, ovvero l'ultima edizione dello standard Javascript già implementata su tutti i browser odierni, beh, non è neanche così male. Applicando alcune modifiche chirurgiche al linguaggio per ovviare ad alcuni dei macroscopici errori derivati dall'eccessiva fretta iniziale con la quale è stato concepito, otterremmo qualcosa di buono ed usabile, mantenendoci "vicini" alle tecnologie native del web!

Come poter applicare queste modifiche chirurgiche? Come nel caso di GWT e simili, facendo scrivere lo sviluppatore in un altro linguaggio... ma questa volta, un linguaggio estremamente simile a Javascript, in grado di generare codice Javascript perfettamente intelleggibile per un programmatore, e quanto più idiomatico possibile.

A quale genere di "errori" CoffeeScript prova a porre un rimedio? Di sintassi, prevalentemente. Javascript offre pochissime delle comodità a cui siamo abituati nella maggiorparte degli altri linguaggi evoluti:

  • interpolazione delle stringhe;
  • stringhe multi-riga;
  • argomenti di default;
  • operatore splat;
  • list comprehension;
  • assegnamenti destrutturati;
  • classi OOP.

Inoltre è estremamente verboso... è tutto un ripetersi di graffe, punti e virgola, this, function e return!

class Greeter
  constructor: (@person, options = {}) ->

  greet: (person) ->
    fullName = "#{firstName} #{lastName}"

    if person.name is "Stefano Verna"
      """
      Hello #{fullName},
      This is a multiline string, wow!
      """
    else
      "#{fullName}?! The hell are you?"

person =
  firstName: "Stefano"
  lastName: "Verna"

new Greeter(person).greet()

CoffeeScript propone una soluzione elegante per ciascuna di queste necessità, introducendo dove necessario anche cambiamenti piuttosto pesanti al linguaggio:

  • white-space significance (via le graffe, si va d'indentazione alla Python);
  • return impliciti (everything is an expression);
  • arrow-functions: una nuova sintassi più breve per definire funzioni (->).
  • fat-arrows: una variante dell'arrow-function pensata per forzare un binding automatico sullo scope presente al momento della definizione della funzione stessa (=>).

Il risultato? Sviluppo più rapido e divertente, codice più terso, capace di rendere più chiaro l'intento del programmatore, oltre che la garanzia di produrre codice ECMAScript 3 compatibile (a prova di IE6).

Come usare CoffeeScript

È possibile compilare CoffeeScript in Javascript passando dal package npm coffee-script. Una volta installato, è disponibile un binario coffee, in grado di produrre opzionalmente source-maps:

npm install -g coffee-script
coffee --compile --map --output lib/ src/

Ovviamente sono disponibili plugin per i principali task runner (grunt-contrib-coffeescript, gulp-coffee), in modo tale da poter integrare la compilazione all'interno di un processo di build più ampio.

Salita e discesa di CoffeeScript

Il rilascio di CoffeeScript, inevitabilmente, ha portato ad anni di perplessità e guerre di religione riguardanti la reale necessità di scrivere il proprio codice su un linguaggio così giovane e così simile a Javascript per il solo gusto di aggiungere un po' di zucchero sintattico. Communities più aperte all'innovazione come quella Ruby/Rails hanno invece accolto immediatamente il linguaggio tra i loro tool di riferimento.

A prescindere da opinioni personali, il dato di fatto è che l'adozione di CoffeeScript nel tempo è cresciuta enormemente. Il picco massimo è stato nel 2013, dove si è piazzato in 17esima posizione tra i linguaggi di programmazione più utilizzati: sintomo evidente di una esigenza di modernizzazione e svecchiamento diffusa nella community Javascript.

Dopo la crescita, dal 2013 ad oggi l'utilizzo di CoffeeScript risulta essere in lenta ma progressiva decrescita. La motivazione? Abbastanza ovvia: ECMAScript 6.

ECMAScript

Innanzitutto, cos'è ECMAScript? La definizione formale del linguaggio Javascript, in mano al committee denominato Ecma TC39. Javascript è una delle implementazioni di ECMAScript, così come lo è ActionScript, ad esempio.

La progressione dello standard ECMAScript, così come Javascript stesso, non ha avuto vita facile. Dopo la formalizzazione di ECMAScript 3 nel 1999, la successiva edizione ECMAScript 4 avrebbe dovuto portare a grosse modifiche del linguaggio. La data di completamento fissata nel 2008 è stata però totalmente disattesa e l'edizione abbandonata per divergenze politiche sulla complessità del linguaggio che ne sarebbe risultato.

A seguito di questo fallimento, il committee decide di lavorare su edizioni meno ambiziose, con modifiche più incrementali del linguaggio: il primo risultato concreto è ECMAScript 5, rilasciato nel 2009, che descrive il linguaggio che oggi ritroviamo implementato su tutti i nostri browser (IE9+). Le modifiche più importanti presenti in questa edizione? Getters/setters e "strict" mode.

Il prossimo step pianificato dal committee? La definizione di ECMAScript 6, la cui pubblicazione era stata pianificata per il 2013, ma che dovrebbe effettivamente avvenire verso metà 2015.

E con ECMAScript 6, le cose diventano estremamente interessanti.

ECMAScript 6

Descrivere nel dettaglio tutti i cambiamenti del linguaggio presenti nel nuovo standard sarebbe compito arduo e forse inutile, considerando che esistono ottime risorse già presenti a questo scopo:

Quello che si nota subito però è come la quasi totalità degli aiuti sintattici presenti in CoffeeScript siano stati fedelmente riportati in ES6, mantenendo però totale retro-compatibilità col linguaggio che finora conosciamo. ES6 è, a tutti gli effetti, un super-set aumentato di ES5.

Diamo un occhio ad alcuni dei cambiamenti più evidenti al linguaggio:

Funzioni: splats, default arguments, fat-arrow binding

var foo = (filter = 'all', options = {}) => { 
  // ...
};
var bar = (first, second, ...others) => {
  // ...
};

var object = {
  init() {
    $("a").click((el) => this.handleClick());
  },    
  handleClick() { 
    /* ... */ 
  }
};
  • È finalmente possibile impostare valori di default per gli argomenti di una funzione;
  • È possibile effettuare il globbing dei parametri tramite una sintassi ad-hoc (...), evitando di trattare l'oggetto arguments;
  • La parola chiave function è opzionale: all'interno della definizione di oggetti è possibile utilizzare lo shortcut nomeFunzione() {};
  • Sfruttando la sintassi fat-arrow (=>) è possibile provocare binding forzato di una funzione sul valore corrente di this, con return automatico;

Comprehensions, destructured assignments e lexical scoping

let collection = [ 1, 2, 3 ];
let [first, second] = collection;

for (el of collection) {
    // ...
}

let generatePoint = (x, y) => { x, y }

let { x, y } = generatePoint(10, 20);
  • La creazione di oggetti ed array, oltre che l'estrazione di dati dagli stessi, viene resa estremamente più semplice attraverso assegnamenti destrutturati.
  • È possibile iterare sugli elementi di un array (e non solo) attraverso il costrutto of;
  • La keyword var viene ufficialmente deprecata, in favore di let. Lo scoping delle variabili in Javascript è sempre stato cervellotico, al punto da dover coniare un nuovo termine per descriverlo (*variable-hoisting*). La keyword let risolve i problemi, gestendo lo scoping con gli stessi meccanismi di Ruby e della maggioparte degli altri linguaggi;

Object-oriented classes

class Lion extends Animal {
  constructor(name) {
    this.name = name;
    this.pos = 0;
  }

  move(meters) {
    this.pos += meters;
  }
}

var lion = new Lion("Alex");

Javascript è sempre stato un linguaggio ad ereditarietà prototipale, un concetto spesso poco familiare per chi proveniente da linguaggi ad ereditarietà classica. In ogni progetto/libreria di medie dimensioni diventa praticamente la norma sfruttare (o implementare ex-novo) piccole librerie JS atte a ricreare il comportamento tipico di una classe OOP.

ES6 mette a disposizione una serie di costrutti specifici (class, extends, super) che permettono di ottenere lo stesso risultato, con una maggiore chiarezza d'intenti ed uniformità di scrittura.

String Interpolation e block strings

var fullName = `${firstName} ${lastName}`;

var greeting = `
Hello ${fullName}! This is a multi-
line string!
`;

Ultima, ma assolutamente non ultima, la definizione a backtick delle stringhe, che permette a queste ultime di estendersi su più righe e supportare l'interpolazione di variabili.

...ed è solo la punta dell'iceberg!

I cambiamenti di ES6 non si riducono solo a questo genere di "abbellimenti" cosmetici, ma introduce concetti fino ad oggi totalmente assenti, capaci di rivoluzionare profondamente gli idiomi e le possibilità del linguaggio:

  • Meta-programmazione: l'oggetto Proxy fornisce funzionalità simili al classico method_missing di Ruby, permettendo la creazione dinamica di metodi e proprietà su un oggetto (DSL, anyone?);
  • Simboli: ES6 introduce un nuovo tipo primitivo, Symbol, sfruttabile come property-name univoco e privato;
  • Generatori: lo yield Ruby atterra anche su Javascript, permettendo la creazione di oggetti iterabili (e non solo);

È possibile usare ES6 oggi?

Ad oggi, il supporto dei browser ad ES6 è molto limitato: Firefox attualmente è il browser col maggior numero di features implementate, ma sicuramente la strada sarà ancora lunga prima di poter dare per scontata una compatibilità completa ed estesa a buona parte della popolazione dei browser.

C'è una buona notizia però. Progetti come Traceur e 6to5 sono in grado di effettuare il transpile di codice ES6 in semplice Javascript ES5, permettendoci di utilizzare queste funzionalità già oggi, con la promessa, in un futuro prossimo, di poter eliminare in toto il processo di pre-compilazione.

Il progetto 6to5, in particolare, sembra essere il più promettente e supportato dalla community, con la più alta compatibilità rispetto allo standard.

È possibile sfruttare 6to5 in modalità stand-alone, REPL, attraverso i soliti task runner (grunt-6to5, gulp-6to5), e sotto forma di transform per Browserify e Webpack (6to5ify, 6to5-loader). È addirittura sfruttabile in Rails/Sprockets attraverso la gemma sprockets-es6.

TypeScript

Merita una menzione particolare TypeScript, un linguaggio superset di ES5 creato in casa Microsoft. Oltre a dare anch'esso la possibilità di creare classi ad ereditarietà classica, la caratteristica principale del linguaggio è quella di permettere static-typing e compile-time type checking del proprio codice Javascript attraverso annotazioni:

// add.ts
function add(left: number, right: number): number {
  return left + right;
}

add("Foo");
add(1);

var result = add(1, 1);
result.foobar;
$ npm install -g typescript
$ tsc add.ts

add.ts(5,1): error TS2346: Supplied parameters do not match any signature of call target.
add.ts(6,1): error TS2346: Supplied parameters do not match any signature of call target.
add.ts(9,8): error TS2339: Property 'foobar' does not exist on type 'number'.

Per definire strutture complesse, l'approccio alla tipizzazione passa attraverso il concetto di duck-typing: la semantica di un oggetto è determinata dall'insieme dei suoi metodi e delle sue proprietà anziché dal fatto di estendere una particolare classe o implementare una specifica interfaccia:

interface ObjectWithTitle {
  title: string;
}

function printTitle(titledObject: ObjectWithTitle) {
  console.log(titledObject.title);
}

var article = {
  title: "This is awesome!",
  publishedAt: new Date(),
  content: "Foo bar"
};

printTitle(article);

Per iniziare ad utilizzare TypeScript non sono necessari grossi investimenti, considerata:

  • l'opzionalità dello static-typing: tutto ciò che non fornisce annotazioni viene comunque accettato come tipo any.
  • la possibilità di accettare un header di definizioni dei tipi separatamente dal codice, permettendo di aggiungere uno strato di tipizzazione anche su librerie di terze-parti[^1].

[^1]: Il progetto TSD offre un database di header di definizioni TypeScript per le più popolari librerie di terze parti (es. jQuery).

Cosa scegliere?

In Cantiere siamo sempre stati grandi sostenitori di strumenti come Sass e Slim, in grado di rendere il nostro lavoro quotidiano più gradevole, meno ripetitivo e meno soggetto ad errori. CoffeScript rientra perfettamente in questi canoni, e proprio per questo motivo è stato sfruttato internamente per quasi quattro anni (praticamente dal suo lancio ad oggi).

Il lavoro di Jeremy Ashkenas si può dire abbia raggiunto al 100% i suoi obiettivi, ispirando addirittura il lavoro stesso del committee ECMA, al quale bisogna riconoscere la capacità di aver reagito prontamente ai feedback del mondo esterno.

Ad oggi abbiamo avuto modo di lavorare su un solo progetto in ES6. È stato un grande successo: tutti i fastidi e i dolori avvertiti in passato col linguaggio sono scomparsi, e la mancanza di CoffeeScript non si è fatta mai sentire. La sensazione, mai provata prima con Javascript, è stata quella di lavorare finalmente con un linguaggio maturo, capace di grande espressività e in grado di supportarci adeguatamente nello sviluppo di complessi applicativi client-side.

TypeScript è anch'esso un progetto interessante: il type-checking in fase di compilazione potrebbe ridurre la quantità di errori di "interfacciamento" tra gli oggetti, e l'ideale onestamente sarebbe poter usare i due linguaggi in congiuzione l'uno all'altro... purtroppo TypeScript è un superset di ES5, dunque ad oggi i due linguaggi sono incompatibili tra di loro.

Cantiere è abituata a lavorare su linguaggi a tipizzazione dinamica mettendo in sicurezza il codice dalla presenza di errori sui tipi attraverso una suite di test di integrazione quanto più completa possibile. Messi alle strette, tra i vantaggi sicuri di ES6 in termini di comodità e i vantaggi teorici di TypeScript su una minore presenza di errori, scegliamo senza dubbio ECMAScript 6.

Il team di TypeScript, tra l'altro, ha già annunciato l'intenzione di allineare il linguaggio ad ES6 nella sua prossima versione 2.0, dunque il test su strada di TypeScript sarà solo rimandato di qualche mese.

Nelle prossime settimane continueremo ad approfondire alcune delle funzionalità di ES6 più interessanti ed innovative con dei post dedicati.

È tutto per oggi… prossima puntata? Framework JS!

Il momento è finalmente giunto, possiamo scaldare i motori delle guerre di religione. La prossima puntata affronterà il mondo dei framework client-side, cercando di approfondire pro e contro dei tre principali contendenti: Angular, Ember.js e React.

Seguici su Twitter o tramite feed RSS per rimanere aggiornato sulla prossima uscita!