Esempi di codice JavaScript e reazione del framework dell'estensione

Questa pagina fornisce esempi di codice scritti in React e JavaScript per funzioni comuni che potresti voler utilizzare nelle tue estensioni.

Utilizzare l'SDK Looker Extension

Le estensioni devono stabilire una connessione con l'host Looker. In React, questa operazione viene eseguita aggregando l'estensione in un componente ExtensionProvider40. Questo componente stabilisce una connessione con l'host Looker e rende disponibili per l'estensione l'SDK Looker Extension e l'SDK Looker.

import React from 'react'
import { ExtensionProvider40 } from '@looker/extension-sdk-react'
import { DemoCoreSDK } from './DemoCoreSDK'


export const App = () => {
 return (
   <ExtensionProvider40 chattyTimeout={-1}>
     <DemoCoreSDK />
   </ExtensionProvider40>
 )
}

Informazioni generali sui provider di estensioni

I provider di estensioni espongono alle estensioni l'SDK delle estensioni di Looker e l'API SDK. Da quando è stato creato il framework dell'estensione, sono state create versioni diverse del provider di estensioni. Questa sezione spiega la cronologia dei provider di estensioni e perché ExtensionProvider40 è il provider consigliato.

Il primo provider di estensione era ExtensionProvider, che ha esposto entrambi gli SDK Looker, versioni 3.1 e 4.0. Lo svantaggio era che l'inclusione di entrambi gli SDK aumentava le dimensioni del bundle di produzione finale.

È stata creata la pagina ExtensionProvider2. È stato creato perché non aveva senso che un'estensione utilizzasse entrambi gli SDK e costringe lo sviluppatore a scegliere uno o l'altro. Purtroppo, per questo motivo, entrambi gli SDK sono stati inclusi nelle dimensioni del bundle di produzione finale.

Quando l'SDK 4.0 è stato spostato in GA, è stato creato ExtensionProvider40. Il vantaggio di ExtensionProvider40 è che lo sviluppatore non deve scegliere quale SDK utilizzare, perché l'SDK 4.0 è l'unica versione disponibile. Poiché l'SDK 3.1 non è incluso nel bundle finale, le dimensioni del bundle potrebbero essere ridotte.

Per aggiungere funzioni dall'SDK dell'estensione Looker, devi prima ottenere un riferimento all'SDK, che può essere eseguito dal provider o a livello globale. In seguito puoi chiamare le funzioni SDK come faresti in qualsiasi applicazione JavaScript.

  • Per accedere all'SDK dal provider, segui questi passaggi:
  import { ExtensionContext40 } from '@looker/extension-sdk-react'

  export const Comp1 = () => {
    const extensionContext = useContext(
      ExtensionContext40
    )
    const { extensionSDK, coreSDK } = extensionContext
  • Per accedere all'SDK a livello globale (l'estensione deve essere inizializzata prima della chiamata), segui questi passaggi:
    const coreSDK = getCoreSDK()

Ora puoi utilizzare l'SDK come faresti in qualsiasi applicazione JavaScript:

  const GetLooks = async () => {
    try {
      const looks = await sdk.ok(sdk.all_looks('id'))
      // process looks
      . . .
    } catch (error) {
      // do error handling
      . . .
    }
}

Poiché l'estensione viene eseguita in un iframe con sandbox, non puoi navigare altrove all'interno dell'istanza di Looker aggiornando l'oggetto window.location dell'elemento principale. È possibile navigare utilizzando l'SDK dell'estensione Looker.

Questa funzione richiede il diritto navigation.

import { ExtensionContext40 } from '@looker/extension-sdk-react'

. . .

  const extensionContext = useContext(
    ExtensionContext40
  )
  const { extensionSDK } = extensionContext

. . .

  extensionSDK.updateLocation('/browse')

Apertura di una nuova finestra del browser

Poiché l'estensione viene eseguita in un iframe con sandbox, non puoi utilizzare la finestra principale per aprire una nuova finestra del browser. È possibile aprire una finestra del browser utilizzando l'SDK Looker Extension.

Questa funzione richiede il diritto new_window per aprire una nuova finestra in una posizione nell'istanza di Looker attuale oppure il diritto new_window_external_urls per aprire una nuova finestra eseguita su un host diverso.

import { ExtensionContext40 } from '@looker/extension-sdk-react'

. . .

  const extensionContext = useContext(
    ExtensionContext40
  )
  const { extensionSDK } = extensionContext

. . .
  extensionSDK.openBrowserWindow('/browse', '_blank')
. . .
  extensionSDK.openBrowserWindow('https://docs.looker.com/reference/manifest-params/application#entitlements', '_blank')

Routing e link diretti

Quanto segue si applica alle estensioni basate su React.

I componenti ExtensionProvider, ExtensionProvider2 e ExtensionProvider40 creano automaticamente un router React chiamato MemoryRouter da utilizzare. Non tentare di creare un BrowserRouter, poiché non funziona negli iframe con sandbox. Non tentare di creare un HashRouter, poiché non funziona negli iframe con sandbox per la versione non basata su Chromium del browser Microsoft Edge.

Se utilizzi MemoryRouter e nell'estensione usi react-router, il framework di estensione sincronizzerà automaticamente il router dell'estensione con il router host di Looker. Ciò significa che quando la pagina viene ricaricata, l'estensione riceverà una notifica relativa ai clic sui pulsanti avanti e indietro nel browser e del percorso corrente. Ciò significa anche che l'estensione deve supportare automaticamente i link diretti. Guarda gli esempi di estensioni su come utilizzare react-router.

Dati contesto estensione

I dati sul contesto del framework delle estensioni non devono essere confusi con i contesti React.

Le estensioni hanno la possibilità di condividere i dati di contesto tra tutti gli utenti di un'estensione. È possibile utilizzare i dati contestuali per dati che non cambiano di frequente e che non hanno requisiti di sicurezza speciali. Presta attenzione quando scrivi i dati, perché non ci sono blocchi dei dati e l'ultima scrittura vince. I dati di contesto sono disponibili per l'estensione subito dopo l'avvio. L'SDK Looker Extension fornisce funzioni per consentire l'aggiornamento e l'aggiornamento dei dati di contesto.

La dimensione massima dei dati di contesto è di circa 16 MB. I dati di contesto verranno serializzati in una stringa JSON, di conseguenza è necessario tenerne conto anche se utilizzi dati di contesto per la tua estensione.

import { ExtensionContext40 } from '@looker/extension-sdk-react'

. . .

  const extensionContext = useContext(
    ExtensionContext40
  )
  const { extensionSDK } = extensionContext

. . .

  // Get loaded context data. This will reflect any updates that have
  // been made by saveContextData.
  let context = await extensionSDK.getContextData()

. . .

  // Save context data to Looker server.
  context = await extensionSDK.saveContextData(context)

. . .

  // Refresh context data from Looker server.
  context = await extensionSDK.refreshContextData()

Attributi utente

L'SDK Looker Extension fornisce un'API per accedere agli attributi utente di Looker. Esistono due tipi di accesso agli attributi utente:

  • Con ambito: associato all'estensione. Un attributo utente con ambito ha uno spazio dei nomi nell'estensione e l'attributo utente deve essere definito nell'istanza di Looker prima di poter essere utilizzato. Per assegnare lo spazio dei nomi a un attributo utente, fai precedere il nome dell'attributo dal nome dell'estensione. Qualsiasi trattino e '::' nel nome dell'estensione devono essere sostituiti da un trattino basso, in quanto non è possibile utilizzare trattini e due punti nei nomi degli attributi utente.

    Ad esempio, per un attributo utente con ambito denominato my_value utilizzato con un ID estensione my-extension::my-extension deve essere definito il nome di un attributo utente my_extension_my_extension_my_value. Una volta definito, l'attributo utente può essere letto e aggiornato dall'estensione.

  • Globali: si tratta di attributi utente globali di sola lettura. Un esempio è l'attributo utente locale.

Di seguito è riportato un elenco di chiamate API per gli attributi utente:

  • userAttributeGetItem: legge un attributo utente. È possibile definire un valore predefinito che verrà utilizzato se non esiste un valore dell'attributo utente per l'utente.
  • userAttributeSetItem: salva un attributo per l'utente corrente. Non riuscirà per gli attributi utente globali. Il valore salvato è visibile solo all'utente corrente.
  • userAttributeResetItem: reimposta un attributo utente per l'utente corrente sul valore predefinito. Non riuscirà per gli attributi utente globali.

Per accedere agli attributi utente, devi specificare i nomi degli attributi nei diritti global_user_attributes e/o scoped_user_attributes. Ad esempio, nel file manifest del progetto LookML, dovresti aggiungere:

  entitlements: {
    scoped_user_attributes: ["my_value"]
    global_user_attributes: ["locale"]
  }
import { ExtensionContext40 } from '@looker/extension-sdk-react'

. . .

  const extensionContext = useContext(
    ExtensionContext40
  )
  const { extensionSDK } = extensionContext

  // Read global user attribute
  const locale = await extensionSDK.userAttributeGetItem('locale')

  // Read scoped user attribute
  const value = await extensionSDK.userAttributeGetItem('my_value')

  // Update scoped user attribute
  const value = await extensionSDK.userAttributeSetItem('my_value', 'abcd1234')

  // Reset scoped user attribute
  const value = await extensionSDK.userAttributeResetItem('my_value')

Archiviazione locale

Gli iframe con sandbox non consentono l'accesso allo spazio di archiviazione locale del browser. L'SDK Looker Extension consente a un'estensione di leggere e scrivere nello spazio di archiviazione locale della finestra principale. Lo spazio di archiviazione locale ha uno spazio dei nomi all'estensione, il che significa che non può leggere lo spazio di archiviazione locale creato dalla finestra principale o da altre estensioni.

Per utilizzare lo spazio di archiviazione locale è necessario il diritto local_storage.

L'API localhost dell'estensione è asincrona, al contrario dell'API di archiviazione locale del browser sincrono.

import { ExtensionContext40 } from '@looker/extension-sdk-react'

. . .

  const extensionContext = useContext(
    ExtensionContext40
  )
  const { extensionSDK } = extensionContext

  // Read from local storage
  const value = await extensionSDK.localStorageGetItem('my_storage')

  // Write to local storage
  await extensionSDK.localStorageSetItem('my_storage', 'abcedefh')

  // Delete item from local storage
  await extensionSDK.localStorageRemoveItem('my_storage')

Aggiornamento del titolo della pagina

Le estensioni possono aggiornare il titolo della pagina corrente. Per eseguire questa azione non sono necessari diritti.

import { ExtensionContext40 } from '@looker/extension-sdk-react'

. . .

  const extensionContext = useContext(
    ExtensionContext40
  )
  const { extensionSDK } = extensionContext

  extensionSDK.updateTitle('My Extension Title')

Scrittura negli appunti di sistema in corso...

Gli iframe con sandbox non consentono l'accesso agli appunti di sistema. L'SDK per l'estensione Looker consente a un'estensione di scrivere testo negli appunti di sistema. Per motivi di sicurezza, l'estensione non può leggere dagli appunti di sistema.

Per scrivere negli appunti di sistema, devi disporre del diritto use_clipboard.

import { ExtensionContext40 } from '@looker/extension-sdk-react'

. . .

const extensionContext = useContext(
    ExtensionContext40
  )
  const { extensionSDK } = extensionContext

    // Write to system clipboard
    try {
      await extensionSDK.clipboardWrite(
        'My interesting information'
      )
      . . .
    } catch (error) {
      . . .
    }

Incorporamento di dashboard, Look ed esplorazioni

Il framework dell'estensione supporta l'incorporamento di dashboard, Look ed esplorazioni.

Il diritto use_embeds è obbligatorio. Ti consigliamo di utilizzare l'SDK Looker JavaScript Embed per incorporare i contenuti. Per ulteriori informazioni, consulta la documentazione relativa all'incorporamento dell'SDK.

import { ExtensionContext40 } from '@looker/extension-sdk-react'

. . .

  const extensionContext = useContext(
    ExtensionContext40
  )
  const { extensionSDK } = extensionContext

. . .

  const canceller = (event: any) => {
    return { cancel: !event.modal }
  }

  const updateRunButton = (running: boolean) => {
    setRunning(running)
  }

  const setupDashboard = (dashboard: LookerEmbedDashboard) => {
    setDashboard(dashboard)
  }

  const embedCtrRef = useCallback(
    (el) => {
      const hostUrl = extensionContext?.extensionSDK?.lookerHostData?.hostUrl
      if (el && hostUrl) {
        el.innerHTML = ''
        LookerEmbedSDK.init(hostUrl)
        const db = LookerEmbedSDK.createDashboardWithId(id as number)
          .withNext()
          .appendTo(el)
          .on('dashboard:loaded', updateRunButton.bind(null, false))
          .on('dashboard:run:start', updateRunButton.bind(null, true))
          .on('dashboard:run:complete', updateRunButton.bind(null, false))
          .on('drillmenu:click', canceller)
          .on('drillmodal:explore', canceller)
          .on('dashboard:tile:explore', canceller)
          .on('dashboard:tile:view', canceller)
          .build()
          .connect()
          .then(setupDashboard)
          .catch((error: Error) => {
            console.error('Connection error', error)
          })
      }
    },
    []
  )

  return (&#60;EmbedContainer ref={embedCtrRef} /&#62;)

Gli esempi di estensioni utilizzano componenti con stili applicati per fornire uno stile semplice all'iframe generato. Ad esempio:

import styled from "styled-components"

export const EmbedContainer = styled.div`
  width: 100%;
  height: 95vh;
  & > iframe {
    width: 100%;
    height: 100%;
  }

Accesso agli endpoint API esterni

Il framework di estensione fornisce due metodi per accedere agli endpoint API esterni:

  • Il proxy del server: accede all'endpoint tramite il server Looker. Questo meccanismo consente al server Looker di impostare in modo sicuro ID client e chiavi private.
  • Il proxy di recupero: accede all'endpoint dal browser dell'utente. Il proxy è l'UI di Looker.

In entrambi i casi, devi specificare l'endpoint API esterno nel diritto external_api_urls dell'estensione.

Proxy server

L'esempio seguente mostra l'utilizzo del proxy del server per ottenere un token di accesso da utilizzare dal proxy di recupero. Il client ID e il secret devono essere definiti come attributi utente per l'estensione. In genere, quando l'attributo utente è configurato, il valore predefinito è impostato sull'ID client o sul secret.

import { ExtensionContext40 } from '@looker/extension-sdk-react'

. . .

  const extensionContext = useContext(
    ExtensionContext40
  )
  const { extensionSDK } = extensionContext

. . .
  const requestBody = {
    client_id: extensionSDK.createSecretKeyTag('my_client_id'),
    client_secret: extensionSDK.createSecretKeyTag('my_client_secret'),
  },
  try {
    const response = await extensionSDK.serverProxy(
      'https://myaccesstokenserver.com/access_token',
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(requestBody),
      }
    )
    const { access_token, expiry_date } = response.body
. . .
  } catch (error) {
    // Error handling
    . . .
  }

Il nome dell'attributo utente deve essere mappato all'estensione. I trattini devono essere sostituiti da trattini bassi e i caratteri :: devono essere sostituiti con un unico trattino basso.

Ad esempio, se il nome della tua estensione è my-extension::my-extension, gli attributi utente da definire per l'esempio precedente sono i seguenti:

my_extension_my_extension_my_client_id
my_extension_my_extension_'my_client_secret'

Recupera proxy

L'esempio seguente mostra l'utilizzo del proxy di recupero. Utilizza il token di accesso del precedente esempio di proxy del server.

import { ExtensionContext40 } from '@looker/extension-sdk-react'

. . .

  const extensionContext = useContext(
    ExtensionContext40
  )
  const { extensionSDK } = extensionContext

. . .

  try {
    const response = await extensionSDK.fetchProxy(
      'https://myaccesstokenserver.com/myendpoint',
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          Authorization: `Bearer ${accessToken}`,
        },
        body: JSON.stringify({
          some_value: someValue,
          another_value: anotherValue,
        }),
      }
    )
    // Handle success

. . .

  } catch (error) {
    // Handle failure

. . .

  }

Integrazione OAuth

Il framework di estensione supporta l'integrazione con i provider OAuth. È possibile utilizzare OAuth per ottenere un token di accesso per accedere a una determinata risorsa, ad esempio un documento di Fogli Google.

Dovrai specificare l'endpoint del server OAuth nell'diritto extension oauth2_urls. Potresti anche dover specificare altri URL nel diritto external_api_urls.

I framework di estensione supportano i seguenti flussi:

  • Flusso implicito
  • Tipo di concessione del codice di autorizzazione con chiave segreta
  • Verifica e verifica del codice PKCE

Il flusso generale prevede l'apertura di una finestra secondaria che carica una pagina del server OAuth. Il server OAuth autentica l'utente e reindirizza nuovamente al server Looker con ulteriori dettagli che possono essere utilizzati per ottenere un token di accesso.

Flusso implicito:

import { ExtensionContext40 } from '@looker/extension-sdk-react'

. . .

  const extensionContext = useContext(
    ExtensionContext40
  )
  const { extensionSDK } = extensionContext

. . .

    const response = await extensionSDK.oauth2Authenticate(
      'https://accounts.google.com/o/oauth2/v2/auth',
      {
        client_id: GOOGLE_CLIENT_ID!,
        scope: GOOGLE_SCOPES,
        response_type: 'token',
      }
    )
    const { access_token, expires_in } = response

Tipo di concessione del codice di autorizzazione con chiave segreta:

  const authenticateParameters: Record&#60;string, string&#62; = {
    client_id: GITHUB_CLIENT_ID!,
    response_type: 'code',
  }
  const response = await extensionSDK.oauth2Authenticate(
    'https://github.com/login/oauth/authorize',
    authenticateParameters,
   'GET'
  )
  const exchangeParameters: Record&#60;string, string&#62; = {
    client_id: GITHUB_CLIENT_ID!,
    code: response.code,
    client_secret: extensionSDK.createSecretKeyTag('github_secret_key'),
  }
  const codeExchangeResponse = await extensionSDK.oauth2ExchangeCodeForToken(
    'https://github.com/login/oauth/access_token',
    exchangeParameters
  )
  const { access_token, error_description } = codeExchangeResponse

Verifica e verifica del codice PKCE:

import { ExtensionContext40 } from '@looker/extension-sdk-react'

. . .

  const extensionContext = useContext(
    ExtensionContext40
  )
  const { extensionSDK } = extensionContext

. . .

  const authRequest: Record&#60;string, string&#62; = {
    client_id: AUTH0_CLIENT_ID!,
    response_type: 'code',
    scope: AUTH0_SCOPES,
    code_challenge_method:  'S256',
  }
  const response = await extensionSDK.oauth2Authenticate(
    'https://sampleoauthserver.com/authorize',
    authRequest,
    'GET'
  )
  const exchangeRequest: Record&#60;string, string&#62; = {
    grant_type: 'authorization_code',
    client_id: AUTH0_CLIENT_ID!,
    code: response.code,
  }
  const codeExchangeResponse = await extensionSDK.oauth2ExchangeCodeForToken(
    'https://sampleoauthserver.com/login/oauth/token',
    exchangeRequest
  )
  const { access_token, expires_in } = codeExchangeResponse

Spartano

Spartan si riferisce a un metodo per utilizzare l'istanza di Looker come ambiente per esporre le estensioni e solo le estensioni a un insieme designato di utenti. A un utente non standard che accede a un'istanza di Looker verrà visualizzato qualsiasi flusso di accesso configurato dall'amministratore di Looker. Una volta autenticato l'utente, gli verrà presentata un'estensione in base al suo attributo utente landing_page, come mostrato di seguito. L'utente può accedere solo alle estensioni, non a nessun'altra parte di Looker. Se l'utente ha accesso a più estensioni, queste stabiliscono la possibilità dell'utente di passare alle altre estensioni utilizzando extensionSDK.updateLocation. Esiste un metodo specifico di SDK estensione Looker per consentire all'utente di uscire dall'istanza di Looker.

import { ExtensionContext40 } from '@looker/extension-sdk-react'

. . .

  const extensionContext = useContext(
    ExtensionContext40
  )
  const { extensionSDK } = extensionContext

. . .
  // Navigate to another extension
  extensionSDK.updateLocation('/spartan/another::extension')

. . .
  // Logout
  extensionSDK.spartanLogout()

Definizione degli utenti spartani

Per definire un utente non standard, devi creare un gruppo chiamato "Solo estensioni".

Una volta creato il gruppo "solo estensioni", vai alla pagina Attributi utente nella sezione Amministratore di Looker e modifica l'attributo utente landing_page. Seleziona la scheda Valori del gruppo e aggiungi il gruppo "Solo estensioni". Il valore deve essere impostato su /spartan/my_extension::my_extension/, dove my_extension::my_extension è l'ID dell'estensione. Una volta eseguito l'accesso, l'utente verrà indirizzato all'estensione designata.

Suddivisione del codice

La suddivisione del codice è una tecnica in cui il codice viene richiesto solo quando necessario. In genere, blocchi di codice sono associati a route React, in cui ogni route riceve il proprio blocco di codice. In React, questa operazione viene eseguita con i componenti Suspense e React.lazy. Il componente Suspense mostra un componente di fallback mentre viene caricato il blocco di codice. React.lazy è responsabile del caricamento del blocco di codice.

Configurazione della suddivisione del codice:

import { AsyncComp1 as Comp1 } from './Comp1.async'
import { AsyncComp1 as Comp2 } from './Comp2.async'

. . .

                <Suspense fallback={<div>Loading...</div>}>
                  <Switch>
                      <Route path="/comp1">
                        <Comp1 />
                      </Route>
                      <Route path="/comp2">
                        <Comp2 />
                      </Route>
                  </Switch>
                <Suspense>

Il componente caricato tramite caricamento lento viene implementato come segue:

import { lazy } from 'react'

const Comp1 = lazy(
 async () => import(/* webpackChunkName: "comp1" */ './Comp1')
)

export const AsyncComp1 = () => &#60;Home />

Il componente viene implementato come segue. Il componente deve essere esportato come componente predefinito:

const Comp1 = () => {
  return (
    &#60;div&#62;Hello World&#60;/div&#62;
  )
}

export default Comp1

Tremori di alberi

Anche se attualmente gli SDK Looker supportano l'estrazione ad albero, questa funzione deve comunque essere migliorata. Modifichiamo continuamente i nostri SDK per migliorare il supporto tree shaking". Alcune di queste modifiche potrebbero richiedere il refactoring del codice per trarne vantaggio, ma quando necessario, queste modifiche saranno documentate nelle note di rilascio.

Per utilizzare la funzione di scuotimento ad albero, il modulo utilizzato deve essere esportato come esmodule e le funzioni importate devono essere prive di effetti collaterali. L'SDK Looker per TypeScript/JavaScript, la libreria runtime dell'SDK Looker, i componenti dell'interfaccia utente di Looker, l'SDK di estensione di Looker e l'SDK delle estensioni per React soddisfano tutti questi requisiti.

In un'estensione, utilizza l'SDK Looker 4.0 e il componente ExtensionProvider2 o ExtensionProvider40 dell'SDK Extension for React.

Il seguente codice configura il provider dell'estensione. Dovrai indicare al provider quale SDK vuoi:

import { MyExtension } from './MyExtension'
import { ExtensionProvider40 } from '@looker/extension-sdk-react'
import { Looker40SDK } from '@looker/sdk/lib/4.0/methods'
import { hot } from 'react-hot-loader/root'

export const App = hot(() => {

  return (
    &#60;ExtensionProvider2 type={Looker40SDK}&#62;
      &#60;MyExtension /&#62;
    &#60;/ExtensionProvider2&#62;
  )
})

Non utilizzare il seguente stile di importazione nell'estensione:

import * as lookerComponents from `@looker/components`

L'esempio precedente include tutti gli elementi del modulo. ma solo i componenti di cui hai bisogno. Ad esempio:

import { Paragraph }  from `@looker/components`

Glossario

  • Suddivisione del codice: una tecnica per il caricamento lento di JavaScript fino a quando non è effettivamente necessario. Idealmente, dovresti mantenere le dimensioni del bundle JavaScript caricato inizialmente. È possibile farlo utilizzando la suddivisione del codice. Qualsiasi funzionalità non immediatamente richiesta non viene caricata finché non è effettivamente necessaria.
  • IDE - Ambiente di sviluppo integrato. Un editor utilizzato per creare e modificare un'estensione. Alcuni esempi sono Visual Studio Code, Intellij e WebStorm.
  • Scena: in genere una visualizzazione di pagina in Looker. Le scene vengono mappate ai percorsi principali. A volte una scena include scene secondarie mappate ai sottopercorsi lungo il percorso principale.
  • Transpile : il processo di acquisizione del codice sorgente scritto in un linguaggio e di trasformarlo in un altro linguaggio con un livello di astrazione simile. Un esempio è TypeScript in JavaScript.