Pubblica, distribuisci e installa il moderno codice JavaScript per applicazioni più veloci

Migliora le prestazioni attivando l'output e le dipendenze JavaScript moderne.

Oltre il 90% dei browser è in grado di eseguire JavaScript moderno, ma prevalenza di JavaScript legacy rimane una grande fonte di problemi di prestazioni sul web oggi stesso.

JavaScript moderno

Il codice JavaScript moderno non è caratterizzato da codice scritto in uno specifico ECMAScript della specifica, ma piuttosto nella sintassi supportata da tutti i browser. Comprendono i browser web moderni come Chrome, Edge, Firefox e Safari oltre il 90% del mercato dei browser e diversi browser che si basano sugli stessi motori di rendering sottostanti costituiscono un ulteriore 5%. Ciò significa che il 95% del traffico web globale proviene dai browser che supportano le funzioni del linguaggio JavaScript più utilizzate negli ultimi 10 anni, tra cui:

  • Classi (ES2015)
  • Funzioni freccia (ES2015)
  • Generatori (ES2015)
  • Determinazione dell'ambito dei blocchi (ES2015)
  • Destrutturazione (ES2015)
  • Parametri di riposo e diffusione (ES2015)
  • Abbreviazione degli oggetti (ES2015)
  • Asinc/attesa (ES2017)

Le funzionalità nelle versioni più recenti della specifica linguistica hanno in genere meno in modo coerente sui browser moderni. Ad esempio, molti modelli ES2020 ed ES2021 sono supportate solo nel 70% del mercato dei browser, ma la maggior parte delle browser, ma non abbastanza da poter fare affidamento diretto su quelle funzionalità. Questo significa che sebbene "moderno" JavaScript è un bersaglio mobile, ES2017 ha la più ampia gamma di compatibilità del browser includendo la maggior parte delle funzionalità di sintassi moderne di uso comune. In altre parole, ES2017 è oggi la più vicina alla sintassi moderna.

JavaScript precedente

Il codice JavaScript legacy è un codice che evita in modo specifico di utilizzare tutto il linguaggio riportato sopra le funzionalità di machine learning. La maggior parte degli sviluppatori scrive il codice sorgente utilizzando una sintassi moderna, ma compilano tutto con la sintassi precedente per un maggiore supporto dei browser. Compilazione alla sintassi precedente aumenta il supporto del browser, ma l'effetto è spesso più piccolo di quanto crediamo. In molti casi l'assistenza aumenta da circa il 95% al 98%, il tutto a fronte di un costo significativo:

  • La versione precedente di JavaScript è in genere più grande di circa il 20% e più lenta un codice moderno equivalente. Spesso vi sono carenze di strumenti ed errori di configurazione ad ampliare ulteriormente questo divario.

  • Le librerie installate rappresentano fino al 90% della produzione tipica codice JavaScript. Il codice libreria comporta una versione precedente di JavaScript ancora più elevata overhead dovuto al polyfill e alla duplicazione dell'helper che poteva essere evitato mediante la pubblicazione di codice moderno.

JavaScript moderno su npm

Di recente, Node.js ha standardizzato un campo "exports" per definire punti di ingresso per un pacco:

{
  "exports": "./index.js"
}

I moduli a cui fa riferimento il campo "exports" implicano una versione del nodo di almeno 12.8, che supporta ES2019. Ciò significa che qualsiasi modulo a cui viene fatto riferimento utilizzando Il campo "exports" può essere scritto in JavaScript. I consumatori del pacco devono presupponi che i moduli con un campo "exports" contengano codice moderno e transpile se necessaria.

Solo moderna

Se vuoi pubblicare un pacchetto con il codice moderno, lasciandolo fino al del consumatore per gestirli quando lo usano come dipendenza: usa solo campo "exports".

{
  "name": "foo",
  "exports": "./modern.js"
}

Moderno con fallback legacy

Utilizza il campo "exports" insieme a "main" per pubblicare il pacchetto utilizzando un codice moderno, ma include anche una versione di riserva ES5 + CommonJS per browser.

{
  "name": "foo",
  "exports": "./modern.js",
  "main": "./legacy.cjs"
}

Moderna con ottimizzazioni legacy dei bundler ed ESM

Oltre a definire un punto di ingresso CommonJS di riserva, il campo "module" può da utilizzare per puntare a un bundle legacy di riserva simile, ma che utilizza Sintassi del modulo JavaScript (import e export).

{
  "name": "foo",
  "exports": "./modern.js",
  "main": "./legacy.cjs",
  "module": "./module.js"
}

Molti bundler, come Webpack e Rollup, si affidano a questo campo per delle funzionalità dei moduli e abilitare che scuote gli alberi. Si tratta ancora di un bundle legacy che non contiene altro codice moderno, a parte Sintassi import/export, utilizza questo approccio per includere un codice moderno con una di riserva legacy ancora ottimizzati per il raggruppamento.

JavaScript moderno nelle applicazioni

Le dipendenze di terze parti costituiscono la maggior parte della produzione tipica codice JavaScript nelle applicazioni web. Sebbene le dipendenze npm siano sempre state pubblicata come sintassi legacy ES5, questo non è più un presupposto sicuro rischia di subire aggiornamenti delle dipendenze, che danneggiano il supporto del browser nella tua applicazione.

Con un numero crescente di pacchetti npm che passano al codice JavaScript moderno, è importante garantire che gli strumenti di creazione siano configurati per gestirli. C'è un è probabile che alcuni dei pacchetti npm da cui dipendi utilizzino già caratteristiche del linguaggio naturale. Esistono diverse opzioni per usare il codice moderno da npm senza interrompere l'applicazione nei browser meno recenti, ma è fare in modo che il sistema di compilazione trasplichi le dipendenze nella stessa sintassi target come codice sorgente.

webpack

A partire da webpack 5, è possibile configurare la sintassi che verrà utilizzata da webpack durante la generazione di codice per pacchetti e moduli. Questa operazione non o dipendenze, influenza solo la "colla" generato da webpack. Per specificare la destinazione del supporto dei browser, aggiungi un elemento configurazione degli elenchi dei browser al tuo progetto o fallo direttamente nella tua configurazione webpack:

module.exports = {
  target: ['web', 'es2017'],
};

È anche possibile configurare webpack per generare bundle ottimizzati omettere funzioni wrapper non necessarie quando si sceglie come target dei moduli ES moderni. completamente gestito di Google Cloud. Inoltre, il webpack si configura per caricare i bundle di suddivisione del codice utilizzando <script type="module">.

module.exports = {
  target: ['web', 'es2017'],
  output: {
    module: true,
  },
  experiments: {
    outputModule: true,
  },
};

Sono disponibili numerosi plug-in webpack che consentono di compilare e distribuire codice JavaScript moderno pur continuando a supportare i browser precedenti, come il plug-in di Optimize e BabelEsmplug.

Plug-in Optimize

Il plug-in Optimize è un webpack plug-in che trasforma il codice in bundle finale da JavaScript moderno a legacy anziché ogni singolo file di origine. Si tratta di una configurazione indipendente la configurazione webpack in modo che tutto sia JavaScript moderno senza una diramazione speciale per più output o sintassi.

Poiché il plug-in di Optimize opera sui bundle anziché sui singoli moduli, elabora il codice della tua applicazione e le tue dipendenze allo stesso modo. Ciò rende utilizzare le moderne dipendenze JavaScript di npm, perché il loro codice raggruppate e di cui viene eseguito il transpile con la sintassi corretta. Può anche essere più veloce di soluzioni tradizionali che prevedono due passaggi di compilazione, pur generando bundle separati per i browser moderni e legacy. I due gruppi di cofanetti sono progettato per essere caricato pattern/nomodule.

// webpack.config.js
const OptimizePlugin = require('optimize-plugin');

module.exports = {
  // ...
  plugins: [new OptimizePlugin()],
};

Optimize Plugin può essere più veloce ed efficiente di un webpack personalizzato che in genere raggruppano codice moderno e legacy separatamente. it gestisce anche Babel in esecuzione per te e minimizza bundle utilizzando Terser con impostazioni ottimali separate per gli output moderni e legacy. Infine, i polyfill necessari I bundle legacy vengono estratti in uno script dedicato, duplicati o caricati inutilmente nei browser più recenti.

Confronto: il transpile dei moduli di origine viene eseguito due volte rispetto al transpile dei pacchetti generati.

BabelEsmPlugin

BabelEsmPlugin è un webpack che funziona insieme @babel/preset-env per generare versioni moderne di bundle esistenti in modo da distribuire su browser moderni. È la soluzione pronta all'uso più diffusa modulo/nomodule, utilizzato da Next.js e Interfaccia a riga di comando Preact.

// webpack.config.js
const BabelEsmPlugin = require('babel-esm-plugin');

module.exports = {
  //...
  module: {
    rules: [
      // your existing babel-loader configuration:
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
          },
        },
      },
    ],
  },
  plugins: [new BabelEsmPlugin()],
};

BabelEsmPlugin supporta un'ampia gamma di configurazioni Webpack, esegue due build ampiamente separate dell'applicazione. La compilazione due volte può richiedere un po' di tempo in più per le applicazioni di grandi dimensioni, ma questa tecnica consente BabelEsmPlugin per un'integrazione perfetta nelle configurazioni Webpack esistenti e li rende una delle opzioni più convenienti disponibili.

Configura Babel-loader per transpile node_modules

Se utilizzi babel-loader senza uno dei due plug-in precedenti, è richiesto un passaggio importante per utilizzare il modello npm JavaScript moderno moduli. La definizione di due configurazioni babel-loader separate consente di per compilare automaticamente le caratteristiche del linguaggio moderno di node_modules per ES2017, mentre continui a trasmettere il tuo codice proprietario con Babel. i plug-in e i preset definiti nella configurazione del progetto. Non generare bundle moderni e legacy per una configurazione modulo/nomodule, ma rendere possibile l'installazione e l'uso di pacchetti npm contenenti codice JavaScript moderno senza danneggiare i browser meno recenti.

webpack-plugin-modern-npm utilizza questa tecnica per compilare dipendenze npm che hanno un campo "exports" nei relativi package.json, poiché possono contenere sintassi moderna:

// webpack.config.js
const ModernNpmPlugin = require('webpack-plugin-modern-npm');

module.exports = {
  plugins: [
    // auto-transpile modern stuff found in node_modules
    new ModernNpmPlugin(),
  ],
};

In alternativa, puoi implementare la tecnica manualmente nel webpack automatica verificando se è presente un campo "exports" in package.json i moduli man mano che vengono risolti. Omissione della memorizzazione nella cache per brevità, un'operazione potrebbe avere il seguente aspetto:

// webpack.config.js
module.exports = {
  module: {
    rules: [
      // Transpile for your own first-party code:
      {
        test: /\.js$/i,
        loader: 'babel-loader',
        exclude: /node_modules/,
      },
      // Transpile modern dependencies:
      {
        test: /\.js$/i,
        include(file) {
          let dir = file.match(/^.*[/\\]node_modules[/\\](@.*?[/\\])?.*?[/\\]/);
          try {
            return dir && !!require(dir[0] + 'package.json').exports;
          } catch (e) {}
        },
        use: {
          loader: 'babel-loader',
          options: {
            babelrc: false,
            configFile: false,
            presets: ['@babel/preset-env'],
          },
        },
      },
    ],
  },
};

Quando utilizzi questo approccio, devi assicurarti che la sintassi moderna sia supportata il tuo minificatore. Entrambi Terser e uglify-es. puoi specificare {ecma: 2017} per conservare e, in alcuni casi, generare la sintassi ES2017 durante la compressione e la formattazione.

Aggregazione

L'aggregazione include il supporto integrato per la generazione di più set di bundle come parte una singola build e genera codice moderno per impostazione predefinita. Di conseguenza, l'aggregazione può essere configurata per generare bundle moderni e legacy con i plug-in ufficiali probabilmente stai già utilizzando.

@rollup/plugin-babel

Se utilizzi l'aggregazione, il parametro Metodo getBabelOutputPlugin() (fornite da plug-in ufficiale di Babel) trasforma il codice in pacchetti generati anziché in singoli moduli di origine. L'aggregazione include il supporto integrato per la generazione di più set di bundle come parte una singola build, ciascuna con i propri plug-in. Puoi utilizzarlo per creare bundle diversi per modernità ed eredità passando ciascuno Configurazione del plug-in di output di Babel:

// rollup.config.js
import {getBabelOutputPlugin} from '@rollup/plugin-babel';

export default {
  input: 'src/index.js',
  output: [
    // modern bundles:
    {
      format: 'es',
      plugins: [
        getBabelOutputPlugin({
          presets: [
            [
              '@babel/preset-env',
              {
                targets: {esmodules: true},
                bugfixes: true,
                loose: true,
              },
            ],
          ],
        }),
      ],
    },
    // legacy (ES5) bundles:
    {
      format: 'amd',
      entryFileNames: '[name].legacy.js',
      chunkFileNames: '[name]-[hash].legacy.js',
      plugins: [
        getBabelOutputPlugin({
          presets: ['@babel/preset-env'],
        }),
      ],
    },
  ],
};

Strumenti di creazione aggiuntivi

Rollup e webpack sono estremamente configurabili, il che in genere significa che ogni progetto deve aggiornare la sua configurazione abilitare la sintassi JavaScript moderna nelle dipendenze. Esistono anche strumenti di creazione di livello superiore che prediligono le convenzioni e i valori predefiniti del prodotto, ad esempio Parcel, Snowpack, Vite e WMR. La maggior parte di questi strumenti si suppone che le dipendenze npm possano contenere sintassi moderna e le trasformeranno in i livelli di sintassi appropriati durante la creazione per la produzione.

Oltre ai plug-in dedicati per Webpack e Rollup, le moderne risorse i bundle con versioni precedenti di riserva possono essere aggiunti a qualsiasi progetto utilizzando d'evoluzione. La distruzione è un strumento autonomo che trasforma l'output da un sistema di compilazione per produrre dati legacy le varianti JavaScript, consentendo al raggruppamento e alle trasformazioni di assumere target di output.