Optimizing Angular–minimizzare momentjs & lodash

Partendo dal presupposto che, anche grazie ad Angular, ci siamo liberati dal fardello jQuery, oggi ci sono alcune librerie javascript utili, molto popolari, ma che tendono a occupare più risorse del dovuto, ovvero:

  • Banda (in particolare su 3g 4g)
  • Cpu lato client (che, come minimo, deve parsarsi il js
  • Cpu lato sviluppatore (che deve inserire tutte le dipendenze)

Due di queste librerie sono:

  • Lodash: contiene funzioni utili per lavorare con i tipi/oggetti javascript, gestire correttamente null e undefined…
  • Momentjs: contiene funzioni e oggetti utili a gestire le date in javascript (come dice la pagina ufficiale, parsare, formattare, validare e manipolare le date)

Progetto base e strumenti

Proviamo ora a creare un progetto Angular ”vuoto” (Angular 9 al momento della scrittura), compilare in produzione con le statistiche e analizzare i bundles generati:

npm install -g @angular/cli
ng new EmptyNg --style=css --routing=false
cd EmptyNg
npm install webpack-bundle-analyzer --save-dev
ng build -prod -stat-json
npx webpack-bundle-analyzer .\dist\EmptyNg\stats-es2015.json

Abbiamo anche installato Webpack-bundle-analyzer in quanto ci permette di capire come sono organizzati i bundle generati da webpack (usato internamente da angular/cli). La situazione è la seguente:

image

per un totale di circa 170Kb di javascript (escludendo i file –es5 usati dai browser )

Aggiungiamo Lodash e momentjs

npm install moment
npm install lodash

e usiamoli, altrimenti il l’algoritmo di treeshaking si accorge che non lo usiamo. Modifichiamo così app.component.ts in questo modo:

import * as moment from 'moment';
import * as _ from 'lodash';
…
title = moment().format('YYYY-MM-DD') + _.isNumber(1);

Vediamo cosa è successo:

ng build -prod -stat-json
npx webpack-bundle-analyzer .\dist\EmptyNg\stats-es2015.json

image

Il nostro applicativo è cresciuto drasticamente, il main è passato da 136Kb a 547Kb!
Ma vediamo come risolvere la cosa.

Minimizzare Momentjs

Subito si può osservare che, moment ha una grossa parte che sono le configurazioni delle lingue che difficilmente nel nostro progetto andremo a utilizzare (al più ne useremo alcune). Eliminarle si può, ed è anche semplice, basta utilizzare un plugin webpack apposito e passare la configurazione in fase di build di webpack:

  1. installare il plugin:
    npm install moment-locales-webpack-plugin -save-dev
    
  2. creare un file di config di estensione di webpack (nel mio caso extra-webpack.config.js) e modificarlo quanto segue:
    const MomentLocalesPlugin = require('moment-locales-webpack-plugin');
    module.exports = {
       plugins: [
         new MomentLocalesPlugin({
           localesToKeep: ['it', 'en-gb'] //uniche lingue da tenere
         })
       ]
     };
    
  3. aggiungere la configurazione in fase di build. Per far questo modificare angular.json e aggiungere in
    "customWebpackConfig": {
         "path": "./extra-webpack.config.js",
         "replaceDuplicatePlugins": true
    },
    
  4. Occorre estendere il processo di build utilizzando il paccketto
    npm install @angular-builders/custom-webpack
    
  5. e configurare il builder in angular.json:
    "builder": "@angular-builders/custom-webpack:browser"
    

rebuildiamo e vediamo l’output:

image

e voilà. main.js è passato da 547Kb a circa la metà, 262Kb.

Minimizzare Lodash

Lodash è (quasi) una libreria contenente tante funzioni. L’idea è quella di importare le singole funzioni in base alle nostre necessità. Per farlo, occorre cambiare pacchetto npm (da lodash a lodash-es), e modificare gli import per importare le singole funzioni:

    1. Cambiare pacchetto nuget:
npm uninstall lodash
npm install lodash-es
  1. modificare l’import delle funzioni richieste
import * as _ from 'lodash';
import { isNumber } from 'lodash/isNumber';

Questo il risultato:

image

Come si può vedere, lodash è letteralmente sparito. Ovviamente la dimensione di lodash sarà sempre più grande più funzioni andremo ad utilizzare (spoiler: ma è veramente necessario lodash?

Lascia un commento

Debugging Rust with VS code

Ho da poco iniziato a sviluppare con Rust e la scelta dell’editor è ricaduta senza troppi pensieri (confermata anche da commenti) su Visual Studio Code.

Ovviamente per avere syntax highlighting, intellisense, refactoring… è necessario installare l’extension di rust:

Inizializziamo un nuovo progetto usando cargo:

cargo new rust_debug_test

E ora configuriamo gli strumenti per debuggare.

1. Plugin C/C++

Di default Rust su windows usa MSVC, è quindi necessario utilizzare l’extension apposita:

2. Aggiungiamo un task per compilare

Prima di poter debuggare la nostra applicazione è necessario compilarla. E’ necessario quindi configurare un task che esegua questa operazione.
Per farlo in modo semplice, apriamo il nostro file src/main.rs in VS code in modo che il plugin crei le strutture di contorno e ci permetta di selezionare velocemente il task appropriato:

CTRL-SHIFT+p
Tasks: Configure task
Rust: Cargo build

nel file tasks.json creato, andiamo ad aggiungere il nome del task che verrà utilizzato successivamente per identificarlo:

 {
    "version": "2.0.0",
    "tasks": [
        {
            "label": "build_application",
            "type": "cargo",
            "subcommand": "build",
            "problemMatcher": [
                "$rustc"
            ]
        }
    ]
} 

Non ci resta che configurare la configurazione di debug nel file launch.json

3. Launch configuration

Per scaffoldare la nostra configurazione basta

Debug menu --> Add configuration
C++ (Windows)

E modificare la configurazione come segue:

 {
    "version": "0.2.0",
    "configurations": [
        {
            "preLaunchTask": "build_application",
            "name": "(Windows) Launch",
            "type": "cppvsdbg",
            "request": "launch",
            "program": "${workspaceFolder}/target/debug/rust_debug_test.exe",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false
        }
    ]
} 

dove:

  • preLaunchTask è il nome del task dato in precedenza per compilare
  • program è il nome dell’eseguibile compilato con cargo, ovvero il nome del package presente nel file cargo.toml

4. F5

and that’s it!

Lascia un commento

Http performance in browsers (+Angular2)

Prendendo spunto da ultime attività lavorative, vorrei evidenziare (molti lo sapranno) come il numero di chiamate http parallele eseguite da un browser (browser != istanza di browser) verso lo stesso server (stesso dominio) sia in numero finito e ridotto, e che la cosa possa peggiorare la performance del vostro applicativo web [http://stackoverflow.com/questions/985431/max-parallel-http-connections-in-a-browser].

Partiamo cronologicamente al contrario:

1 – Angular2

Angular2 è la nuova versione del framework di google, scritta in typescript, ed “enormemente” modulare; basti pensare che:

  • ogni “view” può essere composta dalla definizione di “component” scritto in typescript (=> js), un css e un html ad esso collegato. Questi possono essere embeddati nel file typescript, ma personalmente preferisco tenerli separati.
  • angular promuove la modularizzazione, anche per questioni di performance dovute a shadow dom, change detecion stragegy.. (http://blog.thoughtram.io/angular/2016/02/22/angular-2-change-detection-explained.html)
  • l’accoppiata di dependency injection di ng2 e l’import di moduli esterni con ES2015 è una __FIGATA PAZZESCA__. Ed ora si scrive molto più C#-like (interfacce, classi, ereditarietà … il tutto per il single responsibility principle).

Bene. Ottimo. Fantastico:

many_requests

Il problema è semplice (e di facile risoluzione). Ci sono troppi file da caricare (650+) e… ogni browser può effettuare un massimo di connessioni verso lo stesso dominio (chrome per esempio un massimo di 6).
Tutte le chiamate http vengono dispacciate da questo numero limitato di connessioni, il che rende lungo (9+ secondi) il caricamento dell’applicativo (insieme ovviamente all’elevato numero di handshake).

Soluzione semplice: BUILD (fatelo ‘sempre’ e, soprattutto, pensateci in tempo)

Ad oggi vi sono numerosi strumenti per effettuare la build di un’applicativo web javascript (grunt, gulp, broccoli, webpack…), personalmente mi sto affezionando a webpack, con il seguente risultato:

low_requests

Perfetto. Numero di richieste ridotte al minimo, quantità di dati ridotta, tempi di caricamento abbassati.

2 – Data recomposition

Un altro applicativo web a cui ho/sto lavorando, molto dinamico, permette ad ogni utente di comporre la sua UI in base alle proprie preferenze.
L’utente può decidere

  • quanti grafici aggiungere alla pagina
  • per ogni grafico, quante serie mostrare.

A complicare le cose, i grafici vengono aggiornati in realtime.
Per rendere il tutto il più modulare e indipendente possibile, abbiamo architetturato l’applicativo in modo da recuperare ogni serie con una chiamata http separata. Massima flessibilità ma… alcuni dati:

  • In media (a spanne) un utente apre 2-4 grafici per pagina
  • ogni grafico ha 6 -12 serie scelte dall’utente
  • un utente può aprire apre anche più tab con la stessa applicazione (anche 2 o 3 per pc) su diversi monitor (ok, il contesto è un pò particolare)

Risultato:

  • una connessione utilizzata da signalR (SSE) per tab aperto
  • Le chiamate http, anche se su server è stata implementata una cache abbastanza intelligente, possono richiedere tempo per essere evase. Il tempo di risposta occupa la connessione, non permettendo ad altre serie di essere recuperate velocemente da parte del browser.
  • E’ anche capitato (numero eccessivo di tab aperti) che un tab smettesse di recuperare dati da server finché l’utente non ne chiudesse un’altro.

Soluzione: dipende. Come sempre non c’è una verità. Ci stiamo spostando verso la definizione di “view model” che incapsulino più serie, in modo tale da raggruppare e quindi recuperare più serie in un unica chiamata http. Parrebbe inoltre che i websocket non utilizzi connessioni http (https://samsaffron.com/archive/2015/12/29/websockets-caution-required). Altre idee sono ben accette.

I sistemi software sono complicati, e la conoscenza è oro.

Lascia un commento

JsHint – Permettere variabili globali

Senza entrare nella discussione di quale lint-tool utilizzare per il verificare la “bontà” del codice js scritto, e poichè JsHint è incluso nel plugin di VS WebEssentials, mi sono ritrovato a faccia a faccia con il seguente messaggio:

image

Tale messaggio indica chiaramente che la variabile “_” (lodash nel mio caso) è usata senza che essa venga precedentemente dichiarata. Da qui il mio pensiero è stato:

  1. Vero è che, di buona norma, occorrerebbe evitare di utilizzare variabili globali ed iniettarle tutte tramite injection, però è anche vero che ce ne sono un insieme ($, _, angular, toastr…) che sinceramente si fa “fatica” ogni volta definirle come parametro da iniettare (si, sono molto lazy).
  2. Perchè non esiste lo stesso problema con angular (anch’essa variabile globale)?
  3. E’ possibile in qualche modo nascondere questo messaggio di errore selettivamente per altre librerie?

La risposta è ovviamente positiva e può essere risolta nei seguenti modi:

  1. All’inizio di ogni file .js che utilizza “_”, è possibile aggiungere la direttiva /*global _: false*/, ovvero indica di non considerare “_” in quanto è definito nello scope globale
  2. Da visual studio, sotto il menù “WEB ESSENTIALS-Edit global JSHint settings” è possibile modificare le impostazioni utilizzate dall’utente corrente:
    image
    Il file “.jshintrc” è un file json che, tra le altre cose, definisce quali variabili sono considerate globali:
    image
    Ecco quindi il motivo per il quale “angular” non viene mostrato di default nei messaggi di JsHint. La sezione può essere quindi arricchita in modo da aggiungere altre librerie:
    image
  3. Nel punto precedente tutto funziona perfettamente eccetto che tale setting è a livello di utente e magari “Emix” è una variabile che si vorrebbe considerare globale solo in uno specifico progetto. Per ovviare al problema è possibile creare una copia del file “.jshintrc”, aggiungerla alla solution in modo da utilizzare un set di impostazioni specifiche per il singolo progetto/solution:
    image
    In questo modo inoltre, poichè il file è all’interno della solution, le impostazioni possono essere salvate su source control e quindi condivise tra tutti gli sviluppatori del team.

Lascia un commento

ASP.NET AngularJS seed

Quando iniziai (un po’ di tempo fa a dire il vero) a voler provare/utilizzare AngularJS su alcuni progetti web, mi sono scontrato contro alcuni problemi tra i quali:

  • Non voler cambiare “troppo” le tecnologie utilizzate e conosciute dal team (asp.net, webapi, sql server… e quindi niente node, mongo, php, gruntjs…),
  • Utilizzare Visual Studio e avere una struttura chiara della solution,
  • Creare una SPA, e non solo il motore di binding di ng per le singole view asp.net.

Poichè ai tempi (mesi e mesi fa) non trovai un esempio/template che mi soddisfacesse appieno, in quanto:

  • o veniva usato nodejs e niente vs
  • o l’organizzazione dell’applicazione ng era a cura del developer
  • o gli script ng erano separati dalle view (e per me è una cosa fastidiosa dopo un paio di esperienze tra cui anche la struttura di default di durandaljs)

Ho creato un progetto su github, https://github.com/sierrodc/ASP.NET-MVC-AngularJs-Seed, dove:

  • viene utilizzato nuget solo per le libraries .net; ho scelto di non usarlo per i package js perchè altrimenti non avrei potuto organizzarli come segue
    image
    ovvero una sorta di struttura a strati con le librerie senza dipendenze in Ring0, e con Ring(N) dipendente da Ring(N-1)
  • è configurato angularjs e una semplice SPA con alcune features con la seguente struttura:
    image 
    dove:
    1. emixApp.js definisce l’applicazione angularjs, impostando tutte le dipendenze e le regole di routing (forse lo estrarrò da qui),
    2. la cartella directives contiene tutte le directive custom,
    3. la cartella services contiene i servizi angular, in questo caso solo un proxy alle chiamate verso webapi
    4. la cartella pages che contiene le pagine che costituiscono la SPA, ogni pagina definita tramite coppia #pagina.html, pagina.js#
  • tutte le librerie che ritengo utili (anche per una demo veloce) sono già incluse, configurate e utilizzabili;
    Per una lista completa di librerie incluse faccio riferimento al file readme.md presente su repository (cercherò ti mantenerle il più possibile aggiornate).

E dopo questa condivisione, ogni domanda, suggerimento  o critica è ben accetta.

Lascia un commento