Partager via


Performances et modèles JavaScript

Il y a quelques années, ASP.NET nous a envoyé un compte-rendu de contrôle d’interface utilisateur côté serveur, et il était fluide. Toutefois, ce compte-rendu côté serveur requiert un code de confiance totale. À présent que nous avons migré vers SharePoint et Office 365, un code de confiance totale n’est plus une option. Cela signifie que le rendu de contrôle d’interface utilisateur côté serveur ne fonctionnera plus pour nous.

Pourtant, les entreprises ont encore besoin des fonctionnalités personnalisées de l’interface utilisateur pour leurs sites Web et applications. Cela signifie que les fonctionnalités personnalisées de l’interface utilisateur doivent être déplacées du côté client depuis le côté serveur.

JavaScript côté client est désormais idéal pour le rendu de contrôle d’interface utilisateur.

Modèles JavaScript

Étant donné que JavaScript côté client est le chemin d’accès, quelles sont les meilleures méthodes pour implémenter JavaScript côté client ? Comment commencer ?

Il existe plusieurs options :

Option Description
Incorporations de code JavaScript Site.UserCustomActions ou Web.UserCustomActions autorisent l’inclusion de script directement dans le balisage de page. Ceci est utilisé dans le modèle de chargeur décrit ci-dessous
Modèles d’affichage S’applique à Affichage et Recherche. Vous n’avez pas à déployer de code hébergé par une application ou un fournisseur. Il s’agit simplement d’un fichier JavaScript qui peut être téléchargé sur la bibliothèque de styles (par exemple) pour personnaliser les affichages. Vous pouvez créer n’importe quel affichage requis à l’aide de JavaScript
Compléments hébergés SharePoint Utilise JSOM pour communiquer sur le web hôte ou sur le web complément. Il permet d’accéder au Proxy Web pour les appels domaine croisés
Compléments hébergés par le fournisseur Permet la création d’applications complexes sur un large éventail de piles technologiques - tout en conservant l’intégration sécurisée avec SharePoint
JSLink Vous pouvez charger un ou plusieurs fichiers JavaScript dans de nombreux composants OOTB et affichages
Module ScriptEditor Inclure un script directement ou charger via des balises de script avec des indications pour créer des applications complexes page unique hébergées entièrement sur le site SharePoint

Ne pensez pas que vous êtes bloqué dans ces choix si vous pensez qu’une autre option pourrait être plus adaptée à votre situation.

Performance JavaScript

À chaque étape du processus de développement, il est important de garder la performance à l’esprit. Voici quelques composants qui font une grande différence au niveau de la performance JavaScript :

Option Description
Réduire le nombre de demandes Moins de demandes signifie moins d’aller-retours au serveur, réduisant ainsi le temps de latence.
Récupérer uniquement les données dont vous avez besoin Réduisez la quantité de données envoyées via réseau. Réduit également la charge du serveur.
Offrir une bonne expérience de chargement de page Conserver votre interface utilisateur réactive à l’utilisateur. Par exemple, mettre à jour les menus dans la page avant de démarrer le téléchargement de 100 + enregistrements.
Utiliser des appels asynchrones et modèles lorsque possible Les sondages ont un impact plus important sur la performance que les appels asynchrones ou rappels.
La mise en cache est essentielle La mise en cache réduit davantage la charge sur le serveur tout en améliorant immédiatement la performance.
Se préparer pour plus d’affichage de la page que prévu Une page d’accueil lourde en données est OK quand vous avez uniquement un certain nombre de clics. Mais si vous obtenez des milliers de clics, cela peut impacter la performances.

Que fait mon code

Pour la performance, il est important de savoir ce que fait votre code à tout moment. Cela vous permet d’identifier des façons d’améliorer l’efficacité. Voici quelques méthodes efficaces pour cela.

Réduire le nombre de demandes

Toujours effectuer les demandes les plus petites et le moins possible. Chaque demande que vous éliminez réduit la charge de performance sur le serveur et sur le client. Et envoyant des demandes plus petites réduit encore plus la charge de performance.

Il existe plusieurs méthodes pour réduire les demandes et réduire la taille de la demande.

  • Limiter le nombre de fichiers JavaScript en production. Séparer vos fichiers JavaScript fonctionne bien pour le développement, mais pas si bien pour la production. Combiner vos fichiers JavaScript dans un seul fichier JavaScript ou dans aussi peu de fichiers JavaScript que possible.
  • Réduire la taille des fichiers. Réduire vos fichiers JavaScript de production en supprimant des sauts de ligne, les espaces blancs et les commentaires. Il existe plusieurs programmes JavaScript minify et sites Web qui vous permettent de réduire considérablement la taille des fichiers JavaScript.
  • Utilisez la mise en cache du fichier navigateur pour réduire les demandes. LeModèle de chargeur mis à jour ci-dessous est un bon moyen pour développer cette idée.

Récupérer uniquement les données dont vous avez besoin

Lorsque vous demandez des données, n’oubliez pas de cibler vos demandes sur ce dont vous avez réellement besoin. Télécharger un article entier pour obtenir le titre, par exemple, va réduire la performances.

  • Utilise le filtrage du serveur, sélections et limites pour réduire le trafic sur le réseau.
  • Ne demande pas tous les articles lorsque vous ne souhaitez que les cinq premiers, par exemple.
  • Ne demande pas le conteneur de propriété entier, si vous ne voulez qu’une propriété. Dans un exemple, un script ne nécessitait qu’une propriété, mais a demandé le conteneur de propriété entier, qui s’avéra être 800 Ko. N’oubliez pas également que la taille d’un objet peut changer au fil du temps, donc ce qui est actuellement uniquement quelques kilo-octets peut devenir mégaoctets plus tard dans le cycle de vie du produit.

Ne pas demander les données que vous ignorerez

Si vous récupérez plus de données que vous utilisez en réalité, utilisez l’opportunité pour mieux filtrer votre requête initiale.

  • Demandez uniquement les champs dont vous avez besoin, par exemple, nom et adresse, et non l’intégralité d’un enregistrement.
  • Effectuez des demandes spécifiques filtrées. Par exemple, si vous souhaitez une liste des articles disponibles, indiquez Titre, DatedePublication et Auteur. Laissez le reste des champs vierges.

Offrir une bonne expérience utilisateur

Une interfaces utilisateur saccadée et incohérente a un impact sur non seulement la performance, mais aussi la performance perçue. Écrivez votre code de manière à donner une expérience optimale.

  • Utilisez un spinner pour indiquer que les éléments sont en train de charger ou prennent du temps.
  • Comprendre l’ordre d’exécution de votre code et le mettre en forme pour une expérience utilisateur optimale. Par exemple, si vous envisagez de récupérer un grand nombre de données à partir du serveur, et vous souhaitez changer l’interface utilisateur en masquant un menu, masquez tout d’abord le menu. Ceci permet de minimiser une expérience d’interface utilisateur décalée pour l’utilisateur.

Tous les éléments sont asynchrones

Chaque activité code dans le navigateur devrait être envisagée comme asynchrone. Vos fichiers chargent dans un ordre donné, vous devez attendre que le DOM charge et vos demandes vers SharePoint seront traitées à des vitesses différentes.

  • Comprendre comment votre code fonctionne dans le temps.
  • Utiliser des événements et rappels au lieu d’effectuer des sondages.
  • Utilisez les promesses. Dans la bibliothèque jQuery ils sont indiqués comme objets différés. Il existe des concepts similaires dans Q, WinJS et ES6.
  • Utilisez l’appel asynchrone plutôt qu’un appel non asynchrone.
  • Utilisez l’asynchrone à chaque fois qu’il peut y avoir un délai :
    • Pendant une demande AJAX.
    • Pendant toute manipulation DOM significative.

Les modèles asynchrones améliorent les performances et la réactivité et permettent l’enchaînement efficace d’actions dépendantes.

Mise en cache côté client

La mise en cache côté client est une des améliorations les moins connues que vous pouvez apporter à votre code.

Il existe trois endroits où vous pouvez mettre en cache vos données :

Option Description
Stockage de session Stocke les données sous la forme d’une paire clé/valeur chez le client. Il s’agit de stockage par session qui est toujours stocké sous forme de chaînes.

JSON.stringify() convertit vos objets JavaScript en chaînes, ce qui permet de stocker des objets.
Stockage local Stocke les données sous la forme d’une paire clé/valeur chez le client. Ceci est permanent par session et est toujours stocké sous forme de chaînes.

JSON.stringify() convertit vos objets JavaScript en chaînes, ce qui permet de stocker des objets.
Base de données locale Stocke les données relationnelles chez le client. Utilise fréquemment SQL-Lite comme moteur de base de données.

Le stockage de base de données local n’est pas toujours disponible sur tous les navigateurs . Vérifiez la prise en charge du navigateur cible

Lors de la mise en cache, n’oubliez pas les limites de stockage disponibles et l’actualisation de vos données.

  • Si vous avez atteint la fin de votre limite de stockage, cela peut être judicieux de supprimer les données mises en cache anciennes ou moins importantes.
  • Différents types de données peuvent devenir obsolètes plus rapidement que d’autres. Une liste d’articles d’informations peut être obsolète dans cinq à dix minutes, mais le nom du profil d’un utilisateur peut souvent être en toute sécurité mis en cache pour 24 heures ou plus.

Le stockage local et de session ne dispose pas d’expiration intégrée, mais les cookies expirent. Vous pouvez lier vos données stockées à un cookie pour ajouter une expiration à votre espace de stockage local et de session. Vous pouvez également créer un wrapper de stockage qui inclut une date d’expiration et le consulter dans votre code.

Le prix de la popularité

À quelle fréquence votre page est-elle affichée ? Dans le scénario classique, la page d’accueil d’entreprise est définie en tant que page de lancement pour tous les navigateurs dans l’organisation. Puis soudainement vous recevez beaucoup plus de trafic plus que vous aviez imaginé Chaque octet de contenu prend soudainement une proportion considérable dans la bande passante que consomme votre page d’accueil et sur la performance du serveur .

La solution : allégez votre page d’accueil et ajoutez des liens vers d’autres contenus.

Les tableaux de bord lourds en données sont également éligibles pour une application hébergée par le fournisseur dont vous pouvez modifier l’échelle séparément.

Le modèle chargeur

L’objectif du modèle chargeur consiste à offrir un moyen pour incorporer un nombre inconnu de scripts à distance dans un site sans avoir à mettre à jour le site. Les mises à jour peuvent être effectuées sur le CDN à distance et mettrons à jour tous les sites.

Le modèle chargeur construit une URL avec horodatage à la fin afin que le fichier ne soit pas mis en cache. Il configure jQuery comme une dépendance sur le fichier chargeur, puis exécute une fonction dans le chargeur. Ainsi, votre code JavaScript personnalisé est chargé après le chargement de jQuery .

PnP-dev\Samples\Core.JavaScript\Core.JavaScript.Embedder\Program.cs :

static void Main(string[] args)
{
    ContextManager.WithContext((context) =>
        // this is the script block that will be embedded into the page
        // in practice this can be done during provisioning of the site/web
        // make sure to include ';' at end to play nice with page embedding
        // using the script on demand feature built into SharePoint we load jQuery, then our remote loader(pnp-loader.js or pnp-loader-cached.js) file using a dependency
        var script = @"(function (loaderFile, nocache) {
                                var url = loaderFile + ((nocache) ? '?' + encodeURIComponent((new Date()).getTime()) : '');
                                SP.SOD.registerSod('pnp-jquery.js', 'https://localhost:44324/js/jquery.js');
                                SP.SOD.registerSod('pnp-loader.js', url);
                                SP.SOD.registerSodDep('pnp-loader.js', 'pnp-jquery.js');
                                SP.SOD.executeFunc('pnp-loader.js', null, function() {});
                        })('https://localhost:44324/pnp-loader.js', true);";


        // this version of the script along with pnp-loaderMDS.js (or pnp-loaderMDS-cached.js) handles pages where the minimum download strategy is active
        var script2 = @"ExecuteOrDelayUntilBodyLoaded(function () {
                            var url = 'https://localhost:44324/js/pnp-loaderMDS.js?' + encodeURIComponent((new Date()).getTime());
                            SP.SOD.registerSod('pnp-jquery.js', 'https://localhost:44324/js/jquery.js');
                            SP.SOD.registerSod('pnp-loader.js', url);
                            SP.SOD.registerSodDep('pnp-loader.js', 'pnp-jquery.js');
                            SP.SOD.executeFunc('pnp-loader.js', null, function () {
                                if (typeof pnpLoadFiles === 'undefined') {
                                    RegisterModuleInit('https://localhost:44324/js/pnp-loaderMDS.js', pnpLoadFiles);
                                } else {
                                    pnpLoadFiles();
                                }
                            });
                        });";

        // load the collection of existing links
        var links = context.Site.RootWeb.UserCustomActions;
        context.Load(links, ls => ls.Include(l => l.Title));
        context.ExecuteQueryRetry();

        // this block handles deleting previous test custom actions
        var doDelete = false;

        foreach (var link in links.ToArray().Where(l => l.Title.Equals("MyTestCustomAction", StringComparison.OrdinalIgnoreCase)))
        {
            link.DeleteObject();
            doDelete = true;
        }

        if (doDelete)
        {
            context.ExecuteQueryRetry();
        }

        // now we embed our script into the user custom action
        var newLink = context.Site.RootWeb.UserCustomActions.Add();
        newLink.Title = "MyTestCustomAction";
        newLink.Description = "Doing some testing.";
        newLink.ScriptBlock = script2;
        newLink.Location = "ScriptLink";
        newLink.Update();
        context.ExecuteQueryRetry();
    });
}

Le SP.SOD.registerSodDep('pnp-loader.js', 'pnp-jquery.js'); configure la dépendance et SP.SOD.executeFunc('pnp-loader.js', null, function() {}); force jQuery à charger complètement avant de charger JavaScript personnalisé.

Le newLink.ScriptBlock = script2; et newLink.Location = "ScriptLink"; sont les parties principales dans l’ajout de ceci dans l’action du client utilisateur.

Le fichier pnp-loader.js charge ensuite une liste des fichiers JavaScript, avec une promesse pouvant être exécutée lors de la charge de chaque fichier.

PnP-dev\Samples\Core.JavaScript\Core.JavaScript.CDN\js\pnp-loader.js :

(function () {

    var urlbase = 'https://localhost:44324';
    var files = [
        '/js/pnp-settings.js',
        '/js/pnp-core.js',
        '/js/pnp-clientcache.js',
        '/js/pnp-config.js',
        '/js/pnp-logging.js',
        '/js/pnp-devdashboard.js',
        '/js/pnp-uimods.js'
    ];

    // create a promise
    var promise = $.Deferred();

    // this function will be used to recursively load all the files
    var engine = function () {

        // maintain context
        var self = this;

        // get the next file to load
        var file = self.files.shift();

        var fullPath = urlbase + file;

        // load the remote script file
        $.getScript(fullPath).done(function () {
            if (self.files.length > 0) {
                engine.call(self);
            }
            else {
                self.promise.resolve();
            }
        }).fail(self.promise.reject);
    };

    // create our "this" we will apply to the engine function
    var ctx = {
        files: files,
        promise: promise
    };

    // call the engine with our context
    engine.call(ctx);

    // give back the promise
    return promise.promise();

})().done(function () {
    /* all scripts are loaded and I could take actions here */
}).fail(function () {
    /* something failed, take some action here if needed */
});

Le fichier pnp-loader.js ne met pas en cache, ce qui fonctionne très bien pour un environnement de développement. Le fichier pnp-loader-cached.js remplace la fonction $.getScript par une fonction $.ajax ce qui autorise la mise en cache des fichiers du navigateur et est idéal pour la production.

From PnP-dev\Samples\Core.JavaScript\Core.JavaScript.CDN\js\pnp-loader.js

    // load the remote script file
    $.ajax({
        type: 'GET',
        url: fullPath,
        cache: true,
        dataType: 'script'
    }).done(function () {
        if (self.files.length > 0) {
            engine.call(self);
        }
        else {
            self.promise.resolve();
        }
    }).fail(self.promise.reject);

Ce modèle facilite le déploiement et la mise à jour de sites. Il est particulièrement utile lors du déploiement ou de la mise à jour au sein de milliers de collections de sites.

Mise en cache de l’utilisateur actuel

Si les informations utilisateur sont déjà mises en cache, cette fonction obtient les données à partir du cache de session. Si les informations utilisateur ne sont pas stockées dans le cache, il reçoit les informations utilisateur dont nous avons besoin et les stocke dans le cache.

Cette formule utilise également Différé (version de promesse jQuery ). Si nous obtenons les données à partir du cache ou à partir du serveur, le Différé est résolu. Si une erreur est détectée, le Différé est rejeté.

À partir de PnP-dev\Samples\Core.JavaScript\Core.JavaScript.CDN\js\pnp-core.js :

    getCurrentUserInfo: function (ctx) {

        var self = this;

        if (self._currentUserInfoPromise == null) {

            self._currentUserInfoPromise = $.Deferred(function (def) {

                var cachingTest = $pnp.session !== 'undefined' && $pnp.session.enabled;

                // if we have the caching module loaded
                if (cachingTest) {
                    var userInfo = $pnp.session.get(self._currentUserInfoCacheKey);
                    if (userInfo !== null) {
                        self._currentUserInfo = userInfo;
                        def.resolveWith(ctx || self._currentUserInfo, [self._currentUserInfo]);
                        return;
                    }
                }

                // send the request and allow caching
                $.ajax({
                    method: 'GET',
                    url: '/_api/SP.UserProfiles.PeopleManager/GetMyProperties?$select=AccountName,DisplayName,Title',
                    headers: { "Accept": "application/json; odata=verbose" },
                    cache: true
                }).done(function (response) {

                    // we also parse and add some custom properties as an example
                    self._currentUserInfo = $.extend(response.d,
                        {
                            ParsedLoginName: $pnp.core.getUserIdFromLogin(response.d.AccountName)
                        });

                    if (cachingTest) {
                        $pnp.session.add(self._currentUserInfoCacheKey, self._currentUserInfo);
                    }

                    def.resolveWith(ctx || self._currentUserInfo, [self._currentUserInfo]);

                }).fail(function (jqXHR, textStatus, errorThrown) {

                    console.error('[PNP]=>[Fatal Error] Could not load current user data data from /_api/SP.UserProfiles.PeopleManager/GetMyProperties. status: ' + textStatus + ', error: ' + errorThrown);
                    def.rejectWith(ctx || null);
                });
            });
        }

        return this._currentUserInfoPromise.promise();
    }
}

Modèle de mise en cache à l’aide d’Asynchrone et Différé

Un autre modèle de mise en cache est accessible dans pnp-clientcache.js storageTest, qui provient du storageTest modernisé. Il contient des fonctions pour ajouter, récupérer, supprimer et getOrAdd qui renvoient les données mises en cache si elles sont dans le cache, ou récupèrent les données à partir du serveur et les ajoutent au cache si le cache ne les enregistrent pas, ce qui évite l’écriture répétitive de coded ans la fonction d’appel. Utilise JSON.parse pour tester l’expiration, car l’expiration n’est pas une fonctionnalité de stockage local. _createPersistable stocke l’objet JavaScript dans le cache de stockage local.

À partir de PnP-dev\Samples\Core.JavaScript\Core.JavaScript.CDN\js\pnp-clientcache.js :

// adds the client cache capability
caching: {

    // determine if we have local storage once
    enabled: storageTest(),

    add: function (/*string*/ key, /*object*/ value, /*datetime*/ expiration) {

        if (this.enabled) {
            localStorage.setItem(key, this._createPersistable(value, expiration));
        }
    },

    // gets an item from the cache, checking the expiration and removing the object if it is expired
    get: function (/*string*/ key) {

        if (!this.enabled) {
            return null;
        }

        var o = localStorage.getItem(key);

        if (o == null) {
            return o;
        }

        var persistable = JSON.parse(o);

        if (new Date(persistable.expiration) <= new Date()) {

            this.remove(key);
            o = null;

        } else {

            o = persistable.value;
        }

        return o;
    },

    // removes an item from local storage by key
    remove: function (/*string*/ key) {

        if (this.enabled) {
            localStorage.removeItem(key);
        }
    },

    // gets an item from the cache or adds it using the supplied getter function
    getOrAdd: function (/*string*/ key, /*function*/ getter) {

        if (!this.enabled) {
            return getter();
        }

        if (!$.isFunction(getter)) {
            throw 'Function expected for parameter "getter".';
        }

        var o = this.get(key);

        if (o == null) {
            o = getter();
            this.add(key, o);
        }

        return o;
    },

    // creates the persisted object wrapper using the value and the expiration, setting the default expiration if none is applied
    _createPersistable: function (/*object*/ o, /*datetime*/ expiration) {

        if (typeof expiration === 'undefined') {
            expiration = $pnp.core.dateAdd(new Date(), 'minute', $pnp.settings.localStorageDefaultTimeoutMinutes);
        }

        return JSON.stringify({
            value: o,
            expiration: expiration
        });
    }
},

Pour une utilisation d’asynchrone et de différé plus complexes, vous pouvez voir le tableau de bord développeur dans pnp-clientcache.js

Ressources

Voir aussi

Exemples