Vai al contenuto principale

Decoratori del TC39 Standards Track in Babel

· Lettura di 8 min
Traduzione Beta Non Ufficiale

Questa pagina è stata tradotta da PageTurner AI (beta). Non ufficialmente approvata dal progetto. Hai trovato un errore? Segnala problema →

Babel 7.1.0 supporta finalmente la nuova proposta dei decoratori: puoi provarla utilizzando il plugin @babel/plugin-proposal-decorators 🎉.

Un po' di storia

I decoratori furono proposti per la prima volta da Yehuda Katz più di tre anni fa. TypeScript ha introdotto il supporto per i decoratori nella versione 1.5 (2015) insieme a molte funzionalità ES6. Alcuni framework importanti, come Angular e MobX, iniziarono a utilizzarli per migliorare l'esperienza degli sviluppatori: ciò rese i decoratori popolari e diede alla comunità un falso senso di stabilità.

Babel implementò per la prima volta i decoratori nella versione 5, ma li rimosse in Babel 6 perché la proposta era ancora in evoluzione. Logan Smyth creò un plugin non ufficiale (babel-plugin-transform-decorators-legacy) che replicava il comportamento di Babel 5; in seguito è stato spostato nel repository ufficiale di Babel durante la prima alpha di Babel 7. Questo plugin utilizzava ancora la vecchia semantica dei decoratori, perché non era ancora chiaro quale sarebbe stata la nuova proposta.

Da allora, Daniel Ehrenberg e Brian Terlson sono diventati co-autori della proposta insieme a Yehuda Katz, e la proposta è stata quasi completamente riscritta. Non tutto è stato ancora deciso, e al momento non esiste un'implementazione conforme.

Babel 7.0.0 ha introdotto un nuovo flag per il plugin @babel/plugin-proposal-decorators: l'opzione legacy, il cui unico valore valido era true. Questo cambiamento di rottura era necessario per fornire un percorso di transizione graduale dalla versione Stage 1 della proposta a quella attuale.

In Babel 7.1.0 stiamo introducendo il supporto per questa nuova proposta, ed è abilitato per impostazione predefinita quando si utilizza il plugin @babel/plugin-proposal-decorators. Se non avessimo introdotto l'opzione legacy: true in Babel 7.0.0, non sarebbe stato possibile utilizzare la semantica corretta per impostazione predefinita (che sarebbe equivalente a legacy: false).

La nuova proposta supporta anche i decoratori su campi e metodi privati. Non abbiamo ancora implementato questa funzionalità in Babel (per ogni classe, puoi utilizzare decoratori o elementi privati), ma arriverà molto presto.

Cosa è cambiato nella nuova proposta?

Anche se la nuova proposta sembra molto simile alla precedente, ci sono diverse differenze importanti che le rendono incompatibili.

Sintassi

La vecchia proposta consentiva qualsiasi espressione valida a sinistra (letterali, espressioni di funzione e di classe, espressioni new e chiamate di funzione, accessi a proprietà semplici e calcolate) come corpo di un decoratore. Ad esempio, il seguente codice era valido:

JavaScript
class MyClass {
@getDecorators().methods[name]
foo() {}

@decorator
[bar]() {}
}

Questa sintassi presentava un problema: la notazione [...] veniva utilizzata sia per l'accesso alle proprietà all'interno del corpo del decoratore, sia per definire nomi calcolati. Per prevenire tale ambiguità, la nuova proposta consente solo l'accesso alle proprietà tramite punto (foo.bar), eventualmente con argomenti alla fine (foo.bar()). Se hai bisogno di espressioni più complesse, puoi racchiuderle tra parentesi:

JavaScript
class MyClass {
@decorator
@dec(arg1, arg2)
@namespace.decorator
@(complex ? dec1 : dec2)
method() {}
}

Decoratori di oggetti

La versione precedente della proposta consentiva, oltre ai decoratori per classi e elementi di classe, anche decoratori per membri di oggetti:

JavaScript
const myObj = {
@dec1 foo: 3,
@dec2 bar() {},
};

A causa di alcune incompatibilità con la semantica corrente degli oggetti letterali, sono stati rimossi dalla proposta. Se li stai utilizzando nel tuo codice, resta in attesa perché potrebbero essere reintrodotti in una proposta successiva (tc39/proposal-decorators#119).

Argomenti delle Funzioni Decoratore

Il terzo importante cambiamento introdotto dalla nuova proposta riguarda gli argomenti passati alle funzioni decoratore.

Nella prima versione della proposta, i decoratori degli elementi di classe ricevevano una classe target (o oggetto), una chiave e un descrittore di proprietà — simile a quanto si passerebbe a Object.defineProperty. I decoratori di classe prendevano come unico argomento un costruttore target.

La nuova proposta dei decoratori è molto più potente: i decoratori di elementi prendono un oggetto che, oltre a modificare il descrittore della proprietà, consente di cambiare la chiave, il posizionamento (static, prototype o own) e il tipo (field o method) dell'elemento. Possono anche creare proprietà aggiuntive e definire una funzione (finisher) eseguita sulla classe decorata.

I decoratori di classe prendono un oggetto contenente i descrittori di ogni singolo elemento della classe, consentendo di modificarli prima della creazione della classe.

Aggiornamento

Date queste incompatibilità, non è possibile utilizzare i decoratori esistenti con la nuova proposta: ciò renderebbe la migrazione molto lenta, poiché le librerie esistenti (MobX, Angular, ecc.) non possono essere aggiornate senza introdurre cambiamenti di rottura. Per ovviare a questo problema, abbiamo pubblicato un pacchetto utility che avvolge i decoratori nel tuo codice. Dopo averlo eseguito, puoi modificare in sicurezza la tua configurazione Babel per utilizzare la nuova proposta 🎉.

Puoi aggiornare i tuoi file con un one-liner:

npx wrap-legacy-decorators src/file-with-decorators.js --decorators-before-export --write

Se il tuo codice viene eseguito solo in Node, o se stai aggregando il tuo codice con Webpack o Rollup, puoi evitare di iniettare la funzione wrapper in ogni file utilizzando una dipendenza esterna:

npm install --save decorators-compat
npx wrap-legacy-decorators src/file-with-decorators.js --decorators-before-export --external-helpers --write

Per maggiori informazioni, puoi leggere la documentazione del pacchetto.

Domande Aperte

Non tutto è stato ancora deciso: i decoratori sono una funzionalità molto ampia e definirli nel modo migliore possibile è complesso.

Dove Posizionare i Decoratori sulle Classi Esportate?

tc39/proposal-decorators#69

La proposta dei decoratori è tornata più volte su questa domanda: i decoratori dovrebbero essere posizionati prima o dopo la parola chiave export?

JavaScript
export @decorator class MyClass {}

// or

@decorator
export class MyClass {}

La domanda sottostante è se la parola chiave export faccia parte della dichiarazione della classe o sia un "wrapper". Nel primo caso dovrebbe venire dopo i decoratori, poiché i decoratori si trovano all'inizio della dichiarazione; nel secondo dovrebbe venire prima, perché i decoratori fanno parte della dichiarazione della classe.

Come Fare Interagire Sicuramente i Decoratori con gli Elementi Privati?

tc39/proposal-decorators#129, tc39/proposal-decorators#133

I decoratori sollevano un'importante preoccupazione di sicurezza: se è possibile decorare elementi privati, allora i nomi privati (che possono essere considerati come le "chiavi" degli elementi privati) potrebbero essere divulgati. Esistono diversi livelli di sicurezza da considerare:

  1. I decoratori non dovrebbero divulgare accidentalmente nomi privati. Il codice dannoso non dovrebbe poter "rubare" nomi privati da altri decoratori in alcun modo.

  2. Solo i decoratori applicati direttamente agli elementi privati potrebbero essere considerati attendibili: i decoratori di classe dovrebbero essere in grado di leggere e scrivere elementi privati?

  3. Privacy rigorosa (uno degli obiettivi della proposta dei campi di classe) significa che gli elementi privati dovrebbero essere accessibili solo dall'interno della classe: i decoratori dovrebbero avere accesso ai nomi privati? Dovrebbe essere possibile decorare solo elementi pubblici?

Queste domande richiedono ulteriori discussioni prima di essere risolte, ed è qui che entra in gioco Babel.

Il Ruolo di Babel

Seguendo la tendenza dell'articolo Cosa sta succedendo con la proposta della pipeline (|>), con il rilascio di Babel 7 stiamo iniziando a utilizzare la nostra posizione nell'ecosistema JS per aiutare ancora di più gli autori delle proposte, dando agli sviluppatori la possibilità di testare e fornire feedback sulle diverse varianti delle proposte.

Per questo motivo, insieme all'aggiornamento di @babel/plugin-proposal-decorators abbiamo introdotto una nuova opzione: decoratorsBeforeExport, che consente agli utenti di provare sia export @decorator class C {} che @decorator export default class.

Introdurremo anche un'opzione per personalizzare il vincolo di privacy degli elementi privati decorati. Queste opzioni saranno obbligatorie finché i membri di TC39 non prenderanno una decisione, così da poter impostare il comportamento predefinito su quanto specificato dalla proposta finale.

Se stai utilizzando direttamente il nostro parser (@babel/parser, precedentemente babylon) puoi già usare l'opzione decoratorsBeforeExport nella versione 7.0.0:

JavaScript
const ast = babylon.parse(code, {
plugins: [
["decorators", { decoratorsBeforeExport: true }]
]
})

Utilizzo

Per l'utilizzo in Babel stesso:

npm install @babel/plugin-proposal-decorators --save-dev
babel.config.json
{
"plugins": ["@babel/plugin-proposal-decorators", { "decoratorsBeforeExport": true }]
}

Consulta la documentazione di @babel/plugin-proposal-decorators per ulteriori opzioni.

Il Tuo Ruolo

Come sviluppatore JavaScript, puoi aiutare a delineare il futuro del linguaggio. Puoi testare le varie semantiche in esame per i decoratori e fornire feedback agli autori delle proposte. Abbiamo bisogno di sapere come li utilizzi nei progetti reali! Puoi anche scoprire perché sono state prese alcune decisioni di progettazione leggendo le discussioni nelle issue e i verbali delle riunioni nel repository della proposta.

Se vuoi provare i decoratori subito, puoi sperimentare le diverse opzioni dei preset nel nostro repl!