Bundling JavaScript avanzato

Il raggruppamento di JavaScript moduli per ottenere prestazioni migliori consiste nel ridurre due elementi:

  1. Numero di richieste server.
  2. Dimensione di tali richieste server.

In un’applicazione modulare, il numero di richieste server può raggiungere le centinaia. Ad esempio, la schermata seguente mostra solo l'inizio dell'elenco dei moduli JavaScript caricati nella home page di un'installazione pulita.

Nessun bundle

Unione e raggruppamento

Per ridurre il numero di richieste server, Commerce offre due modi predefiniti: unione e bundling. Queste impostazioni sono disattivate per impostazione predefinita. Puoi attivarli all'interno dell'interfaccia utente di amministrazione in Stores > Impostazioni > Configuration > Advanced > Developer > JavaScript Settings o dalla riga di comando.

Bundling

Raggruppamento di base

Per abilitare il bundling incorporato dalla riga di comando:

php -f bin/magento config:set dev/js/enable_js_bundling 1

Questo è un meccanismo nativo di Commerce che combina tutte le risorse presenti nel sistema e le distribuisce tra bundle delle stesse dimensioni (bundle_0.js, bundle_1.js … bundle_x.js):

Commerce raggruppamento

Meglio, ma il browser carica ancora TUTTI i bundle JavaScript, non solo quelli necessari.

Il bundle Commerce riduce il numero di connessioni per pagina, ma per ogni richiesta di pagina carica tutti i bundle, anche quando la pagina richiesta può dipendere solo da file all'interno di uno o due dei bundle. Le prestazioni migliorano dopo che il browser memorizza in cache i bundle. Tuttavia, poiché il browser carica questi bundle in modo sincrono, la prima visita dell'utente a una vetrina Commerce potrebbe richiedere del tempo per il rendering e danneggiare l'esperienza utente.

Unione di base

Per abilitare l'unione incorporata dalla riga di comando:

php -f bin/magento config:set dev/js/merge_files 1

Questo comando unisce tutti i file JavaScript sincroni in un unico file. L'abilitazione dell'unione senza abilitare anche il bundling non è utile perché Commerce utilizza RequireJS. Se non si abilita il bundle, Commerce unisce solo RequireJS e la relativa configurazione. Quando si abilita sia l'unione che il raggruppamento, Commerce crea un singolo file JavaScript:

Unione reale

Tempi di rendering reali

I precedenti tempi di caricamento in bundle e uniti sono molto simili in un ambiente di sviluppo. Ma nel mondo reale, molte cose possono rallentare il rendering: connessioni lente, soglie di connessione grandi, reti limitate. Inoltre, i dispositivi mobili non eseguono il rendering alla stessa velocità dei desktop.

Per testare e preparare la tua distribuzione in vetrina per il mondo reale, ti consigliamo di testare con il profilo di limitazione nativo di Chrome "Slow 3G". Con Slow 3G, i nostri precedenti tempi di output in bundle ora riflettono le realtà di connessione di molti utenti:

Bundling reale

Con la connettività Slow 3G, sono necessari circa 44 secondi per caricare tutti i bundle per la home page di un'installazione pulita di Commerce.

Lo stesso vale quando si uniscono i bundle in un singolo file. Gli utenti possono ancora attendere circa 42 secondi per il caricamento iniziale della pagina, come illustrato di seguito:

Unione reale

Con un approccio più avanzato al bundle JavaScript, possiamo migliorare questi tempi di caricamento.

Bundling avanzato

Ricorda che l'obiettivo del bundling JavaScript è di ridurre il numero e le dimensioni delle risorse richieste per ogni pagina caricata nel browser. Per farlo, vogliamo creare i nostri bundle in modo che ogni pagina nel nostro store debba scaricare solo un bundle comune e un bundle specifico per ogni pagina a cui si accede.

Un modo per ottenere questo risultato è definire i bundle per tipo di pagina. È possibile categorizzare le pagine di Commerce in diversi tipi di pagina, tra cui Categoria, Prodotto, CMS, Cliente, Carrello e Pagamento. Ogni pagina categorizzata in uno di questi tipi di pagina ha un set diverso di dipendenze del modulo RequireJS. Quando si raggruppano i moduli RequireJS per tipo di pagina, si otterrà solo una manciata di bundle che coprono le dipendenze di qualsiasi pagina del negozio.

Ad esempio, potresti ottenere un bundle per le dipendenze comuni a tutte le pagine, un bundle per le pagine solo CMS, un bundle per le pagine solo catalogo, un altro bundle per le pagine solo ricerca e un bundle per le pagine Pagamento.

Puoi anche creare bundle in base allo scopo: per caratteristiche comuni, funzioni relative ai prodotti, funzioni di spedizione, funzioni di pagamento, imposte e convalide di moduli. Sta a te definire come definire i bundle e la struttura del tuo negozio. Potresti notare che alcune strategie di bundling funzionano meglio di altre.

Un'installazione pulita di Commerce consente di ottenere prestazioni sufficienti suddividendo i bundle per tipo di pagina, ma alcune personalizzazioni potrebbero richiedere un'analisi più approfondita e altre distribuzioni di risorse.

Strumenti richiesti

I seguenti passaggi richiedono l’installazione di e di avere familiarità con i seguenti strumenti:

Codice di esempio

Le versioni complete del codice di esempio utilizzato in questo articolo sono disponibili qui:

Parte 1: Creare una configurazione di bundling

1. Aggiungere un file build.js

Creare un file build.js nella directory principale Commerce. Questo file contiene l’intera configurazione della build per i bundle.

({
    optimize: 'none',
    inlineText: true
})

In seguito, cambieremo l'impostazione optimize: da_ none a uglify2 per minimizzare l'output del bundle. Tuttavia, per il momento, durante lo sviluppo, puoi lasciarla impostata su none per garantire build più veloci.

2. Aggiungere dipendenze, shim, percorsi e mappe RequireJS

Aggiungi i seguenti nodi di configurazione della build RequireJS, deps, shim, paths e map, al file di build:

({
    optimize: 'none',
    inlineText: true,

    deps: [],
    shim: {},
    paths: {},
    map: { "*": {} },
})

3. Aggregare i valori dell’istanza requirements-config.js

In questo passaggio sarà necessario aggregare tutti i più nodi di configurazione deps, shim, paths e map dal file requirejs-config.js dell'archivio nei nodi corrispondenti nel file build.js. A tale scopo, è possibile aprire la scheda Network nel pannello Strumenti per sviluppatori del browser e passare a qualsiasi pagina dell'archivio, ad esempio la home page. Nella scheda Rete verrà visualizzata l'istanza del file requirejs-config.js del tuo archivio nella parte superiore, evidenziata qui:

Configurazione RequireJS

In questo file sono disponibili più voci per ciascuno dei nodi di configurazione (deps, shim, paths, map). Devi aggregare questi valori su più nodi nel singolo nodo di configurazione del file build.js. Ad esempio, se l'istanza requirejs-config.js dell'archivio contiene voci per 15 nodi map separati, sarà necessario unire le voci per tutti i 15 nodi nel singolo nodo map nel file build.js. Lo stesso vale per i nodi deps, shim e paths. Senza uno script per automatizzare questo processo, potrebbe richiedere tempo.

È necessario modificare il percorso mage/requirejs/text in requirejs/text nel nodo di configurazione paths come segue:

({
    //...
    paths: {
        //...
        "text": "requirejs/text"
    },
})

4. Aggiungi un nodo di moduli

Alla fine del file build.js, aggiungi l'array module[] come segnaposto per i bundle che definirai per la vetrina in un secondo momento.

({
    optimize: 'none',
    inlineText: true,

    deps: [],
    shim: {},
    paths: {},
    map: { "*": {} },

    modules: [],
})

5. Recuperare le dipendenze RequireJS

Puoi recuperare tutte le dipendenze del modulo RequireJS dai tipi di pagina del tuo store utilizzando:

  1. PhantomJS dalla riga di comando (supponendo che sia installato PhantomJS).
  2. RequireJS nella console del browser.

Per utilizzare PhantomJS:

Nella directory principale Commerce, creare un nuovo file denominato deps.js e copiarlo nel codice seguente. Questo codice utilizza PhantomJS per aprire una pagina e attendere che il browser carichi tutte le risorse di pagina. Quindi restituisce tutte le dipendenze RequireJS per una determinata pagina.

"use strict";
var page = require('webpage').create(),
    system = require('system'),
    address;

if (system.args.length === 1) {
    console.log('Usage: $phantomjs deps.js url');
    phantom.exit(1);
} else {
    address = system.args[1];
    page.open(address, function (status) {
        if (status !== 'success') {
            console.log('FAIL to load the address');
        } else {
            setTimeout(function () {
                console.log(page.evaluate(function () {
                    return Object.keys(window.require.s.contexts._.defined);
                }));
                phantom.exit();
            }, 5000);
        }
    });
}

Aprire un terminale all'interno della directory principale Commerce ed eseguire lo script per ogni pagina dell'archivio che rappresenta un tipo di pagina specifico:

phantomjs deps.js url-to-specific-page > text-file-presentation-pagetype-dependencies

Ad esempio, ecco quattro pagine dell’archivio esempi a tema Luma che rappresentano i quattro tipi di pagina che utilizzeremo per creare i nostri quattro bundle (homepage, categoria, prodotto, carrello):

phantomjs deps.js http://m2.loc/ > bundle/homepage.txt
phantomjs deps.js http://m2.loc/women/tops-women/jackets-women.html > bundle/category.txt
phantomjs deps.js http://m2.loc/beaumont-summit-kit.html > bundle/product.txt
phantomjs deps.js http://m2.loc/checkout/cart/?SID=m2tjdt7ipvep9g0h8pmsgie975 > bundle/cart.txt (prepare a shopping cart)
..............

Per utilizzare la console del browser:

Se non desideri utilizzare PhantomJS, puoi eseguire il seguente comando dalla console del browser durante la visualizzazione di ogni tipo di pagina nella vetrina:

Object.keys(window.require.s.contexts._.defined)

Questo comando (utilizzato nello script PhantomJS) crea lo stesso elenco di dipendenze RequireJS e le visualizza nella console del browser. Lo svantaggio di questo approccio è che dovrai creare un pacchetto personalizzato o file di testo di tipo pagina.

6. Formattare e filtrare l’output

Dopo aver unito le dipendenze RequireJS in file di testo di tipo pagina, è possibile utilizzare il comando seguente in ogni file di dipendenza di tipo pagina per sostituire le virgole nei file con nuove righe:

sed -i -e $'s/,/\\\n/g' bundle/category.txt
sed -i -e $'s/,/\\\n/g' bundle/homepage.txt
sed -i -e $'s/,/\\\n/g' bundle/product.txt
....

È inoltre necessario rimuovere tutti i mixin per ciascun file perché i mixin duplicano le dipendenze. Utilizza il seguente comando su ciascun file di dipendenza:

sed -i -e 's/mixins\!.*$//g' bundle/homepage.txt
sed -i -e 's/mixins\!.*$//g' bundle/category.txt
sed -i -e 's/mixins\!.*$//g' bundle/product.txt
...

7. Identificare bundle univoci e comuni

L'obiettivo è creare un bundle comune di JavaScript file necessari per tutte le pagine. In questo modo il browser deve caricare solo il bundle comune insieme a uno o più tipi di pagina specifici.

Aprire un terminale nella directory principale Commerce e utilizzare il comando seguente per verificare che siano presenti dipendenze che è possibile suddividere in bundle separati:

sort bundle/*.txt |uniq -c |sort -n

Questo comando unisce e ordina le dipendenze trovate nei file bundle/*.txt. L’output mostra anche il numero di file che contengono ciascuna dipendenza:

1 buildTools,
1 jquery/jquery.parsequery,
1 jsbuild,
2 jquery/jquery.metadata,
2 jquery/validate,
2 mage/bootstrap,
3 jquery
3 jquery/ui
3 knockoutjs/knockout
...

Questo output mostra che buildTools è una dipendenza in uno solo dei file bundle/*.txt. La dipendenza jquery/jquery.metadata è in due (2) file e es6-collections è in tre (3) file.

L’output mostra solo tre tipi di pagina (home page, categoria e prodotto), che indicano:

  • Tre dipendenze sono univoche per un solo tipo di pagina (mostrato dal numero 1).
  • Altre tre dipendenze si verificano su due tipi di pagina (mostrati dal numero 2).
  • Le ultime tre dipendenze sono comuni a tutti e tre i tipi di pagina (mostrati dal numero 3).

Questo ci dice che probabilmente possiamo migliorare le velocità di caricamento delle pagine del nostro negozio suddividendo le nostre dipendenze in un bundle diverso, una volta che sappiamo quali tipi di pagina hanno bisogno di quali dipendenze.

8. Creare un file di distribuzione delle dipendenze

Per individuare i tipi di pagina necessari per le dipendenze, creare un nuovo file nella directory principale Commerce denominata deps-map.sh e copiarlo nel codice seguente:

awk 'END {
 for (R in rec) {
   n = split(rec[R], t, "/")
   if (n > 1)
     dup[n] = dup[n] ? dup[n] RS sprintf("\t%-20s -->\t%s", rec[R], R) : \
       sprintf("\t%-20s -->\t%s", rec[R], R)
   }
 for (D in dup) {
   printf "records found in %d files:\n\n", D
   printf "%s\n\n", dup[D]
   }
 }
{
 rec[$0] = rec[$0] ? rec[$0] "/" FILENAME : FILENAME
}' bundle/*.txt

Puoi anche trovare lo script in https://www.unix.com/shell-programming-and-scripting/140390-get-common-lines-multiple-files.html

Aprire un terminale nella directory principale Commerce ed eseguire il file:

bash deps-map.sh

L’output di questo script, applicato ai tre tipi di pagina di esempio, dovrebbe essere simile al seguente (ma molto più lungo):

bundle/product.txt   -->   buildTools,
bundle/category.txt  -->   jquery/jquery.parsequery,
bundle/product.txt   -->   jsbuild,

bundle/category.txt/bundle/homepage.txt -->    jquery/jquery.metadata,
bundle/category.txt/bundle/homepage.txt -->    jquery/validate,
bundle/category.txt/bundle/homepage.txt -->    mage/bootstrap,

bundle/category.txt/bundle/homepage.txt/bundle/product.txt --> jquery,
bundle/category.txt/bundle/homepage.txt/bundle/product.txt --> jquery/ui,
bundle/category.txt/bundle/homepage.txt/bundle/product.txt --> knockoutjs/knockout,

Queste informazioni sono sufficienti per creare una configurazione di bundle.

9. Creare bundle nel file build.js

Apri il file di configurazione build.js e aggiungi i bundle al nodo modules. Ogni bundle deve definire le seguenti proprietà:

  • name: nome del bundle. Ad esempio, un nome di bundles/cart genera un bundle cart.js in una sottodirectory bundles.

  • create: un flag booleano per creare il bundle (valori: true o false).

  • include: un array di risorse (stringhe) incluse come dipendenze per la pagina. RequireJS traccia tutte le dipendenze e le include nel bundle, a meno che non siano escluse.

  • exclude: array di bundle o risorse da escludere dal bundle.

{
    name: 'bundles/catalog',
    create: true,
    include: [
        'addToWishlist',
        'priceBundle',
        'priceUtils',
        'priceOptions',
        'sticky',
        'productSummary',
        'slide'
    ],
    exclude: [
        'requirejs/require',
        'bundles/default',
        'mage/bootstrap'
    ],
}

In questo esempio vengono riutilizzate mage/bootstrap e requirejs/require risorse, assegnando maggiore priorità ai componenti più importanti e ai componenti da caricare in modo sincrono. I bundle presenti sono:

  • requirejs/require: l'unico bundle caricato in modo sincrono
  • mage/bootstrap: il bundle di bootstrap con i componenti dell'interfaccia utente
  • bundles/default: bundle predefinito richiesto per tutte le pagine
  • bundles/cart: bundle richiesto per la pagina carrello
  • bundles/shipping: bundle comune per il carrello e la pagina di pagamento (supponendo che il pagamento non venga mai aperto direttamente, la pagina di pagamento viene caricata ancora più velocemente se la pagina del carrello è stata aperta in precedenza e il bundle di spedizione è già stato caricato)
  • bundles/checkout—tutto per il pagamento
  • bundles/catalog—tutto per le pagine di prodotti e categorie

Parte 2: generare i bundle

I passaggi seguenti descrivono il processo di base per la generazione di bundle Commerce più efficienti. Puoi automatizzare questo processo come preferisci, ma dovrai comunque utilizzare nodejs e r.js per generare effettivamente i bundle. E se i temi hanno personalizzazioni relative a JavaScript e non possono riutilizzare lo stesso file build.js, potrebbe essere necessario creare diverse build.js configurazioni per tema.

1. Generare siti di archiviazione statici

Prima di generare i bundle, esegui il comando di distribuzione statica:

php -f bin/magento setup:static-content:deploy -f -a frontend

Questo comando genera distribuzioni statiche dell'archivio per ogni tema e lingua impostati. Ad esempio, se utilizzi il tema Luma e un tema personalizzato con le lingue in inglese e francese, puoi generare quattro distribuzioni statiche:

  • …luma/en_US
  • …luma/fr_FR
  • …custom/en_US
  • …custom/fr_FR

Per generare bundle per tutti i temi e le impostazioni internazionali dello store, ripeti i passaggi seguenti per ciascun tema e ciascuna impostazione locale dello store.

2. Spostare il contenuto dell'archivio statico in una directory temporanea

Innanzitutto, devi spostare il contenuto statico dalla directory di destinazione ad una directory temporanea perché RequireJS sostituisce tutto il contenuto all’interno della directory di destinazione.

mv pub/static/frontend/Magento/{theme}/{locale} pub/static/frontend/Magento/{theme}/{locale}_tmp

Ad esempio:

mv pub/static/frontend/Magento/luma/en_US pub/static/frontend/Magento/luma/en_US_tmp

3. Eseguire r.js optimizer

Eseguire quindi r.js optimizer sul file build.js dalla directory radice di Commerce. I percorsi di tutte le directory e dei file sono relativi alla directory di lavoro.

r.js -o build.js baseUrl=pub/static/frontend/Magento/luma/en_US_tmp dir=pub/static/frontend/Magento/luma/en_US

Questo comando genera i bundle in una sottodirectory bundles della directory di destinazione, che in questo caso restituisce pub/static/frontend/Magento/luma/en_US/bundles.

L’elenco dei contenuti della nuova directory del bundle potrebbe essere simile al seguente:

ll pub/static/frontend/Magento/luma/en_US/bundles
total 1900
drwxr-xr-x  2 root root    4096 Mar 28 11:24 ./
drwxr-xr-x 70 root root    4096 Mar 28 11:24 ../
-rw-r--r--  1 root root  116417 Mar 28 11:24 cart.js
-rw-r--r--  1 root root  187090 Mar 28 11:24 catalog.js
-rw-r--r--  1 root root  307619 Mar 28 11:24 checkout.js
-rw-r--r--  1 root root 1240608 Mar 28 11:24 default.js
-rw-r--r--  1 root root   74233 Mar 28 11:24 shipping.js

4. Configurare RequireJS per l’utilizzo dei bundle

Per ottenere RequireJS per utilizzare i bundle, aggiungi un callback onModuleBundleComplete dopo il nodo modules nel file build.js:

[
    {
       //...
       exclude: [
           'requirejs/require',
           'bundles/default',
           'bundles/checkout',
           'bundles/cart',
           'bundles/shipping',
           'mage/bootstrap'
       ],
   },
],
bundlesConfigOutFile: `${config.dir}/requirejs-config.js`,
onModuleBundleComplete: function(data) {
    if (this.bundleConfigAppended) {
        return;
    }
    this.bundleConfigAppended = true;

    // bundlesConfigOutFile requires a simple require.config call in order to modify the configuration
    const bundleConfigPlaceholder = `
(function (require) {
require.config({});
})(require);
    `;

    fs.appendFileSync(this.bundlesConfigOutFile, bundleConfigPlaceholder);
}

5. Rieseguire il comando di distribuzione

Esegui il comando seguente per distribuire:

r.js -o app/design/frontend/Magento/luma/build.js baseUrl=pub/static/frontend/Magento/luma/en_US_tmp dir=pub/static/frontend/Magento/luma/en_US

Apri requirejs-config.js nella directory pub/static/frontend/Magento/luma/en_US per verificare che RequireJS abbia aggiunto il file con le chiamate di configurazione bundling:

require.config({
    bundles: {
        "bundles/default": ["mage/template", "mage/apply/scripts", "mage/apply/main", "mage/mage", "mage/translate", "mage/loader"],
        "bundles/cart": ["Magento_Ui/js/lib/validation/utils", "Magento_Ui/js/lib/validation/rules", "Magento_Ui/js/lib/validation/validation"]
    }
}
NOTE
Durante la configurazione dei bundle, assicurati di inserire le chiamate requirejs.config() nell'ordine in cui desideri che vengano eseguite, in quanto le chiamate vengono eseguite nell'ordine in cui compaiono.

6. Verificare i risultati

Una volta caricata la pagina, noterai che il browser sta caricando diverse dipendenze e bundle. Ad esempio, di seguito sono riportati i risultati per il profilo "Slow 3G":

Velocità doppia

Il tempo di caricamento della pagina per una home page vuota è ora il doppio rispetto all'utilizzo del bundling nativo Commerce. Ma possiamo fare ancora meglio.

7. Ottimizzare i bundle

Anche se sono compressi, i file JavaScript sono ancora grandi. Minimizzarli con RequireJS, che utilizza un uglifier per minimizzare JavaScript e ottenere buoni risultati.

Per abilitare l'ottimizzatore nel file build.js, aggiungere uglify2 come valore per la proprietà di ottimizzazione nella parte superiore del file build.js:

({
    optimize: 'uglify2',
    inlineText: true
})

I risultati possono essere significativi:
Tre volte più veloce

I tempi di caricamento sono ora tre volte più veloci rispetto al bundling nativo di Commerce.

recommendation-more-help
c0c5bbed-4957-4162-81bc-120c837a1894