Padrões e desempenho do JavaScript

As partes do Suplemento do SharePoint deste artigo são diretrizes herdadas. A desativação abaixo não desativa as outras APIs ou plataformas discutidas aqui.

Importante

Os suplementos do SharePoint atingiram o fim da vida útil no SharePoint Online em 2 de abril de 2026. Os suplementos hospedados no SharePoint e hospedados pelo provedor foram desativados e não funcionam mais no SharePoint Online, incluindo nuvens governamentais. As diretrizes de desenvolvimento, implantação e uso de suplementos são mantidas para referência histórica e para cenários aplicáveis do Servidor do SharePoint, não para novas soluções do SharePoint Online.

Os suplementos do Servidor do SharePoint implantados por meio de um catálogo de aplicativos local não são desativados por essa alteração. A aquisição de Suplementos do SharePoint do marketplace público terminou em 2 de abril de 2026 para o Servidor do SharePoint.

Essa desativação não desativa APIs REST do SharePoint, CSOM, JSOM ou SPFx (Estrutura do SharePoint). Para o SharePoint Online, use SPFx para extensões de interface do usuário e o Microsoft Entra ID para autenticação para aplicativos externos. Consulte o anúncio de desativação e as diretrizes de migração de suplemento.

Anos atrás, ASP.NET nos deu renderização de controle de interface do usuário do lado do servidor, e foi bom. Essa renderização do lado do servidor, no entanto, requer código de confiança total. Agora que fizemos a transição para o SharePoint e o Office 365, o código de confiança total não é mais uma opção. Isso significa que a renderização do controle de interface do usuário do lado do servidor não funcionará mais para nós.

No entanto, as empresas ainda precisam de funcionalidade de interface do usuário personalizada para seus sites e aplicativos. Isso significa que a funcionalidade personalizada da interface do usuário deve ser movida do lado do servidor para o lado do cliente.

O JavaScript do lado do cliente agora é o caminho a seguir para a renderização de controle de interface do usuário.

Padrões JavaScript

Como o JavaScript do lado do cliente é o caminho, quais são as melhores maneiras de implementar o JavaScript do lado do cliente? Como começar?

Existem várias opções:

Opção Descrição
Inserção do JavaScript Site.UserCustomActions ou Web.UserCustomActions permitem a inclusão de script diretamente na marcação de página. Isso é usado no Padrão do Carregador discutido abaixo
Modelos de exibição Aplica-se a exibições e pesquisa. Você não precisa implantar nenhum tipo de código hospedado de aplicativo ou provedor. É simplesmente um arquivo JavaScript que pode ser carregado (por exemplo) na biblioteca de estilos para personalizar exibições. Você pode criar qualquer exibição necessária usando JavaScript
SharePoint Hospedado Add-Ins Usa JSOM para se comunicar com o host da Web ou o suplemento. Ele fornece acesso ao Proxy da Web para chamadas entre domínios
Provedor Hospedado Add-Ins Permite a criação de aplicativos complexos em uma variedade de pilhas de tecnologia, mantendo a integração segura com o SharePoint
JSLink Permite que você carregue um ou mais arquivos JavaScript em muitas Web Parts e exibições OOTB
Webpart do Editor de Scripts Inclua script diretamente ou carregado por meio de marcas de script com marcação para criar aplicativos complexos de página única hospedados inteiramente no site do SharePoint

Não pense que você está preso a essas escolhas se achar que uma opção diferente seria melhor para sua situação.

Desempenho do JavaScript

Em cada etapa do processo de desenvolvimento, é importante manter o desempenho em mente. Aqui estão algumas coisas que fazem uma grande diferença no desempenho do JavaScript:

Opção Descrição
Reduzir o número de solicitações Menos solicitações significam menos idas e voltas para o servidor, reduzindo a latência.
Recupere apenas os dados de que você precisa Reduza a quantidade de dados enviados por fio. Também reduz a carga do servidor.
Fornecer uma boa experiência de carregamento de página Mantenha sua interface do usuário responsiva ao usuário. Por exemplo, atualize os menus na página antes de iniciar o download de +100 registros.
Use chamadas e padrões assíncronos sempre que possível A sondagem é um fardo mais pesado no desempenho do que usar uma chamada ou retorno de chamada assíncrono.
O cache é fundamental O cache reduz ainda mais a carga no servidor e oferece melhoria imediata de desempenho.
Prepare-se para mais visualizações de página do que você jamais imaginou Uma página de destino com muitos dados é boa quando você tem apenas alguns acessos. Mas se você obtiver milhares de acessos, isso pode realmente afetar o desempenho.

O que meu código está fazendo

Para desempenho, é importante saber o que seu código está fazendo a qualquer momento. Isso permite que você identifique maneiras de melhorar a eficiência. Abaixo estão algumas boas maneiras de fazer exatamente isso.

Reduzir o número de solicitações

Sempre faça o menor e menor número possível de solicitações. Cada solicitação eliminada reduz a carga de desempenho do servidor e do cliente. E fazer solicitações menores reduz ainda mais a carga de desempenho.

Existem várias maneiras de reduzir as solicitações e o tamanho das solicitações.

  • Limite o número de arquivos JavaScript em produção. Separar seus arquivos JavaScript funciona bem para o desenvolvimento, mas não tão bem para a produção. Combine seus arquivos JavaScript em um único arquivo JavaScript ou o menor número possível de arquivos JavaScript.
  • Reduzir o tamanho dos arquivos. Minimize seus arquivos JavaScript de produção removendo quebras de linha, espaços em branco e comentários. Há vários programas e sites de minimização de JavaScript que você pode usar para reduzir significativamente o tamanho dos arquivos JavaScript.
  • Use o cache de arquivos do navegador para reduzir solicitações. O padrão de carregador atualizado abaixo é uma boa maneira de expandir essa ideia.

Recupere apenas os dados de que você precisa

Ao solicitar dados, lembre-se de concentrar suas solicitações no que você realmente precisa. Baixar um artigo inteiro para obter o título, por exemplo, reduzirá bastante o desempenho.

  • Use a filtragem, a seleção e os limites do servidor para minimizar o tráfego por fio.
  • Não solicite todos os artigos quando quiser apenas os cinco primeiros, como outro exemplo.
  • Não peça a bolsa de propriedade inteira se quiser apenas uma propriedade. Em um exemplo, um script precisava de apenas uma propriedade, mas solicitou todo o recipiente de propriedades, que acabou sendo de 800 KB. Lembre-se também de que o tamanho de um objeto pode mudar com o tempo, portanto, o que é apenas alguns kilobytes agora pode se tornar megabytes de tamanho posteriormente no ciclo de vida do produto.

Não solicite dados que serão descartados e não utilizados

Se você recuperar mais dados do que realmente usa, pense nisso como uma oportunidade de filtrar melhor sua consulta inicial.

  • Solicite apenas os campos necessários, como Nome e Endereço, e não o registro inteiro.
  • Fazer solicitações de filtro específicas e deliberadas. Por exemplo, se você quiser listar os artigos disponíveis, obtenha o Título, a DataDePublicação e o Autor. Deixe o restante dos campos fora da solicitação.

Proporcionar uma boa experiência ao usuário

Interfaces de usuário instáveis e inconsistentes afetam não apenas o desempenho, mas também o desempenho percebido. Escreva seu código de forma a proporcionar uma experiência tranquila.

  • Use um controle giratório para indicar que as coisas estão carregando ou demorando.
  • Entenda a ordem de execução do seu código e molde-o para obter a melhor experiência do usuário. Por exemplo, se você planeja recuperar muitos dados do servidor e pretende alterar a interface do usuário ocultando um menu, oculte o menu primeiro. Isso impedirá uma experiência de interface do usuário escalonada para o usuário.

Tudo é assíncrono

Cada atividade de código no navegador deve ser considerada assíncrona. Seus arquivos são carregados em alguma ordem, você deve aguardar o carregamento do DOM e suas solicitações de volta ao SharePoint serão concluídas em velocidades diferentes.

  • Entenda como seu código opera no tempo.
  • Use eventos e retornos de chamada em vez de sondagem.
  • Use promessas. No jQuery, eles são chamados de objetos adiados . Existem conceitos semelhantes em Q, WinJS e ES6.
  • Use a chamada assíncrona em favor da chamada não assíncrona.
  • Use assíncrono sempre que houver um atraso:
    • Durante uma solicitação AJAX.
    • Durante qualquer manipulação significativa do DOM.

Padrões assíncronos melhoram o desempenho e a capacidade de resposta e permitem o encadeamento efetivo de ações dependentes.

Cache do lado do cliente

O cache do lado do cliente é um dos aprimoramentos de desempenho mais frequentes que você pode adicionar ao seu código.

Há três locais diferentes em que você pode armazenar seus dados em cache:

Opção Descrição
Armazenamento de Sessão Armazena dados como um par chave/valor no cliente. Esse é o armazenamento por sessão que é sempre armazenado como cadeias de caracteres.

JSON.stringify() converterá seus objetos JavaScript em strings, o que ajuda a armazenar objetos.
Armazenamento local Armazena dados como um par chave/valor no cliente. Isso é persistente em todas as sessões que são sempre armazenadas como strings.

JSON.stringify() converterá seus objetos JavaScript em strings, o que ajuda a armazenar objetos.
Banco de Dados Local Armazena dados relacionais no cliente. Freqüentemente usa SQL-Lite como mecanismo de banco de dados.

O armazenamento do Banco de Dados Local nem sempre está disponível em todos os navegadores — Verifique o suporte ao navegador de destino

Ao armazenar em cache, lembre-se dos limites de armazenamento disponíveis e da atualização de seus dados.

  • Se você estiver chegando ao fim de seus limites de armazenamento, talvez seja aconselhável remover os dados em cache mais antigos ou menos importantes.
  • Diferentes tipos de dados podem se tornar obsoletos mais rapidamente do que outros. Uma lista de artigos de notícias pode ficar obsoleta em cinco a dez minutos, mas o nome do perfil de um usuário geralmente pode ser armazenado em cache com segurança por 24 horas ou mais.

O armazenamento local e de sessão não tem expiração interna, mas os cookies têm. Você pode vincular seus dados armazenados a um cookie para adicionar expiração ao armazenamento local e de sessão. Você também pode criar um wrapper de armazenamento que inclua uma data de expiração e marcar isso em seu código.

O preço da popularidade

Com que frequência sua página é visualizada? No cenário clássico, a página inicial corporativa é definida como a página de inicialização para todos os navegadores da organização. Então, de repente, você recebe muito mais tráfego do que jamais imaginou. Cada byte de conteúdo é subitamente ampliado no desempenho do servidor e na largura de banda que sua página inicial consome.

A solução: vá leve na página inicial e crie um link para o outro conteúdo.

Painéis com muitos dados também são candidatos a um aplicativo hospedado pelo provedor que pode ser dimensionado de forma independente.

O padrão do carregador

O objetivo do padrão de carregador é fornecer uma maneira de incorporar um número desconhecido de scripts remotos em um site sem precisar atualizá-lo. As atualizações podem ser feitas no CDN remoto e atualizarão todos os sites.

O Padrão do Carregador constrói uma URL com carimbo de data e hora no final para que o arquivo não seja armazenado em cache. Ele configura o jQuery como uma dependência do arquivo do carregador e, em seguida, executa uma função no carregador. Isso garante que o JavaScript personalizado seja carregado depois que o jQuery terminar de carregar.

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();
    });
}

Isso SP.SOD.registerSodDep('pnp-loader.js', 'pnp-jquery.js'); configura a dependência e SP.SOD.executeFunc('pnp-loader.js', null, function() {}); força o jQuery a ser carregado completamente antes de carregar o JavaScript personalizado.

O newLink.ScriptBlock = script2; e newLink.Location = "ScriptLink"; são as partes principais para adicionar isso à ação do usuário e do cliente.

Em seguida, o arquivo pnp-loader.js carrega uma lista de arquivos JavaScript, com uma promessa que pode ser executada quando cada arquivo é carregado.

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 */
});

O arquivo pnp-loader.js não é armazenado em cache, o que funciona bem para um ambiente de desenvolvimento. O arquivo pnp-loader-cached.js substitui a $.getScript função por uma $.ajax função que permite o cache dos arquivos no navegador e é mais adequado para produção.

De 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);

Esse padrão facilita a implantação e as atualizações para sites. Ele é especialmente útil ao implantar ou atualizar em milhares de conjuntos de sites.

Armazenamento do usuário atual em cache

Se as informações do usuário já estiverem armazenadas em cache, essa função obterá os dados do cache da sessão. Se as informações do usuário não forem armazenadas no cache, elas obterão as informações específicas do usuário necessárias e as armazenarão no cache.

Isso também usa Adiado (versão jQuery da promessa). Se obtivermos os dados do cache ou do servidor, o Adiado é resolvido. Se houver um erro, o Adiado será rejeitado.

A 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();
    }
}

Padrão de cache usando aplicativos assíncronos e adiados

Outro padrão de cache pode ser encontrado em pnp-clientcache.js storageTest, que é obtido do modernizador storageTest. Ele contém funções para adicionar, obter, remover e getOrAdd que retornarão os dados armazenados em cache se estiverem no cache ou recuperarão os dados do servidor e os adicionarão ao cache se não estiverem no cache, o que salva a gravação de código repetitivo na função de chamada. get usa JSON.parse para testar a expiração, pois a expiração não é um recurso no armazenamento local. _createPersistable armazena o objeto JavaScript no cache de armazenamento local.

A 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
        });
    }
},

Para um uso mais complexo de assíncrono e adiado, você pode consultar o dashboard do desenvolvedor em pnp-clientcache.js

Recursos

Confira também

Exemplos