Agrupamento e minificação

por Rick Anderson

Agrupamento e minificação são duas técnicas que você pode usar no ASP.NET 4.5 para melhorar o tempo de carregamento da solicitação. O agrupamento e a minificação melhoram o tempo de carregamento reduzindo o número de solicitações para o servidor e reduzindo o tamanho dos ativos solicitados (como CSS e JavaScript.)

A maioria dos navegadores principais atuais limita o número de conexões simultâneas por cada nome de host para seis. Isso significa que, enquanto seis solicitações estão sendo processadas, solicitações adicionais para ativos em um host serão enfileiradas pelo navegador. Na imagem abaixo, as guias de rede das ferramentas de desenvolvedor do IE F12 mostram o tempo para os ativos exigidos pela exibição Sobre de um aplicativo de exemplo.

B/M

As barras cinzas mostram a hora em que a solicitação é enfileirada pelo navegador aguardando o limite de seis conexões. A barra amarela é o tempo de solicitação para o primeiro byte, ou seja, o tempo necessário para enviar a solicitação e receber a primeira resposta do servidor. As barras azuis mostram o tempo necessário para receber os dados de resposta do servidor. Você pode clicar duas vezes em um ativo para obter informações detalhadas de tempo. Por exemplo, a imagem a seguir mostra os detalhes de tempo para carregar o arquivo /Scripts/MyScripts/JavaScript6.js .

Captura de tela que mostra a guia de rede de ferramentas de desenvolvedor A SP dot NET com URLs de solicitação de ativos na coluna esquerda e seus intervalos na coluna à direita.

A imagem anterior mostra o evento Start , que dá a hora em que a solicitação foi enfileirada devido ao limite do navegador do número de conexões simultâneas. Nesse caso, a solicitação foi enfileirada por 46 milissegundos aguardando a conclusão de outra solicitação.

Agrupamento

O agrupamento é um novo recurso no ASP.NET 4.5 que facilita a combinação ou o agrupamento de vários arquivos em um único arquivo. Você pode criar CSS, JavaScript e outros pacotes. Menos arquivos significa menos solicitações HTTP e isso pode melhorar o desempenho de carregamento da primeira página.

A imagem a seguir mostra a mesma exibição de tempo do modo de exibição Sobre mostrado anteriormente, mas desta vez com o agrupamento e a minificação habilitados.

Captura de tela que mostra a guia de detalhes de tempo de um ativo nas ferramentas de desenvolvedor do I E F 12. O evento Start está realçado.

Minificação

A minificação executa uma variedade de otimizações de código diferentes para scripts ou css, como remover espaço em branco desnecessário e comentários e reduzir nomes de variáveis para um caractere. Considere a função JavaScript a seguir.

AddAltToImg = function (imageTagAndImageID, imageContext) {
    ///<signature>
    ///<summary> Adds an alt tab to the image
    // </summary>
    //<param name="imgElement" type="String">The image selector.</param>
    //<param name="ContextForImage" type="String">The image context.</param>
    ///</signature>
    var imageElement = $(imageTagAndImageID, imageContext);
    imageElement.attr('alt', imageElement.attr('id').replace(/ID/, ''));
}

Após a minificação, a função é reduzida para o seguinte:

AddAltToImg = function (n, t) { var i = $(n, t); i.attr("alt", i.attr("id").replace(/ID/, "")) }

Além de remover os comentários e o espaço em branco desnecessário, os seguintes parâmetros e nomes de variáveis foram renomeados (abreviados) da seguinte maneira:

Original Renamed
imageTagAndImageID n
imageContext t
imageElement i

Impacto do agrupamento e da minificação

A tabela a seguir mostra várias diferenças importantes entre listar todos os ativos individualmente e usar o agrupamento e a minificação (B/M) no programa de exemplo.

Usando B/M Sem B/M Alteração
Solicitações de arquivo 9 34 256%
KB Enviado 3.26 11.92 266%
KB Recebido 388.51 530 36%
Tempo de Carregamento 510 MS 780 MS 53%

Os bytes enviados tiveram uma redução significativa com o agrupamento, pois os navegadores são bastante detalhados com os cabeçalhos HTTP que eles aplicam nas solicitações. A redução de bytes recebidos não é tão grande porque os maiores arquivos (Scripts\jquery-ui-1.8.11.min.js e Scripts\jquery-1.7.1.min.js) já estão minificados. Observação: os intervalos no programa de exemplo usaram a ferramenta Fiddler para simular uma rede lenta. (No menu Regras do Fiddler, selecione Desempenho e , em seguida, Simular Velocidades de Modem.)

Depurando JavaScript empacotado e minificado

É fácil depurar seu JavaScript em um ambiente de desenvolvimento (em que o Elemento de compilação no arquivo Web.config está definido debug="true" como ) porque os arquivos JavaScript não são agrupados ou minificados. Você também pode depurar um build de versão em que os arquivos JavaScript são agrupados e minificados. Usando as ferramentas de desenvolvedor do IE F12, você depura uma função JavaScript incluída em um pacote minificado usando a seguinte abordagem:

  1. Selecione a guia Script e, em seguida, selecione o botão Iniciar depuração .
  2. Selecione o pacote que contém a função JavaScript que você deseja depurar usando o botão ativos.
    Captura de tela que mostra a guia Script da ferramenta de desenvolvedor I E F 12. A caixa de entrada Script de Pesquisa, um pacote e uma função de Script Java estão realçados.
  3. Formate o JavaScript minificado selecionando o botão ConfiguraçãoImagem que mostra o ícone do botão Configuração e, em seguida, selecionando Formatar JavaScript.
  4. Na caixa de entrada Pesquisar Script , selecione o nome da função que você deseja depurar. Na imagem a seguir, AddAltToImg foi inserido na caixa de entrada Script de Pesquisa.
    Captura de tela que mostra a guia Script da ferramenta de desenvolvedor I E F 12. A caixa de entrada Pesquisar Script com Adicionar Alt a lmg inserida nela está realçada.

Para obter mais informações sobre como depurar com as ferramentas de desenvolvedor F12, consulte o artigo do MSDN Usando as Ferramentas para Desenvolvedores F12 para depurar erros de JavaScript.

Controlando o agrupamento e a minificação

O agrupamento e a minificação são habilitados ou desabilitados definindo o valor do atributo de depuração no Elemento de compilação no arquivo Web.config . No XML a seguir, debug é definido como true para que o agrupamento e a minificação sejam desabilitados.

<system.web>
    <compilation debug="true" />
    <!-- Lines removed for clarity. -->
</system.web>

Para habilitar o agrupamento e a minificação, defina o debug valor como "false". Você pode substituir a configuração Web.config com a EnableOptimizations propriedade na BundleTable classe . O código a seguir habilita o agrupamento e a minificação e substitui qualquer configuração no arquivo Web.config .

public static void RegisterBundles(BundleCollection bundles)
{
    bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                 "~/Scripts/jquery-{version}.js"));

    // Code removed for clarity.
    BundleTable.EnableOptimizations = true;
}

Observação

EnableOptimizations A menos que seja true ou o atributo de depuração no Elemento de compilação no arquivo Web.config esteja definido falsecomo , os arquivos não serão agrupados ou minificados. Além disso, a versão .min dos arquivos não será usada, as versões de depuração completas serão selecionadas. EnableOptimizationssubstitui o atributo de depuração no elemento de compilação no arquivo Web.config

Usando agrupamento e minificação com ASP.NET Web Forms e páginas da Web

Usando agrupamento e minificação com ASP.NET MVC

Nesta seção, criaremos um projeto MVC ASP.NET para examinar o agrupamento e a minificação. Primeiro, crie um novo projeto de Internet ASP.NET MVC chamado MvcBM sem alterar nenhum dos padrões.

Abra o arquivo App\_Start\BundleConfig.cs e examine o RegisterBundles método usado para criar, registrar e configurar pacotes. O código a seguir mostra uma parte do RegisterBundles método .

public static void RegisterBundles(BundleCollection bundles)
{
     bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                 "~/Scripts/jquery-{version}.js"));
         // Code removed for clarity.
}

O código anterior cria um novo pacote JavaScript chamado ~/bundles/jquery que inclui todos os apropriados (que é depurado ou minificado, mas não .vsdoc) arquivos na pasta Scripts que correspondem à cadeia de caracteres de cartão selvagem "~/Scripts/jquery-{version}.js". Para ASP.NET MVC 4, isso significa que, com uma configuração de depuração, o arquivojquery-1.7.1.js será adicionado ao pacote. Em uma configuração de versão, jquery-1.7.1.min.js será adicionado. A estrutura de agrupamento segue várias convenções comuns, como:

  • Selecionar o arquivo ".min" para versão quando FileX.min.js e FileX.js existirem.
  • Selecionando a versão não ".min" para depuração.
  • Ignorando arquivos "-vsdoc" (como jquery-1.7.1-vsdoc.js), que são usados apenas pelo IntelliSense.

A {version} correspondência de cartão selvagem mostrada acima é usada para criar automaticamente um pacote jQuery com a versão apropriada do jQuery na pasta Scripts. Neste exemplo, o uso de uma cartão selvagem oferece os seguintes benefícios:

  • Permite que você use o NuGet para atualizar para uma versão mais recente do jQuery sem alterar o código de agrupamento anterior ou as referências jQuery em suas páginas de exibição.
  • Seleciona automaticamente a versão completa para configurações de depuração e a versão ".min" para builds de versão.

Usando uma CDN

O código a seguir substitui o pacote jQuery local por um pacote jQuery da CDN.

public static void RegisterBundles(BundleCollection bundles)
{
    //bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
    //            "~/Scripts/jquery-{version}.js"));

    bundles.UseCdn = true;   //enable CDN support

    //add link to jquery on the CDN
    var jqueryCdnPath = "https://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.1.min.js";

    bundles.Add(new ScriptBundle("~/bundles/jquery",
                jqueryCdnPath).Include(
                "~/Scripts/jquery-{version}.js"));

    // Code removed for clarity.
}

No código acima, jQuery será solicitado da CDN enquanto estiver no modo de versão e a versão de depuração do jQuery será buscada localmente no modo de depuração. Ao usar uma CDN, você deve ter um mecanismo de fallback caso a solicitação CDN falhe. O fragmento de marcação a seguir do final do arquivo de layout mostra o script adicionado para solicitar jQuery caso a CDN falhe.

</footer>

        @Scripts.Render("~/bundles/jquery")

        <script type="text/javascript">
            if (typeof jQuery == 'undefined') {
                var e = document.createElement('script');
                e.src = '@Url.Content("~/Scripts/jquery-1.7.1.js")';
                e.type = 'text/javascript';
                document.getElementsByTagName("head")[0].appendChild(e);

            }
        </script> 

        @RenderSection("scripts", required: false)
    </body>
</html>

Criando um pacote

O método de classe IncludeBundle usa uma matriz de cadeias de caracteres, em que cada cadeia de caracteres é um caminho virtual para o recurso. O seguinte código do RegisterBundles método no arquivo App\_Start\BundleConfig.cs mostra como vários arquivos são adicionados a um pacote:

bundles.Add(new StyleBundle("~/Content/themes/base/css").Include(
    "~/Content/themes/base/jquery.ui.core.css",
    "~/Content/themes/base/jquery.ui.resizable.css",
    "~/Content/themes/base/jquery.ui.selectable.css",
    "~/Content/themes/base/jquery.ui.accordion.css",
    "~/Content/themes/base/jquery.ui.autocomplete.css",
    "~/Content/themes/base/jquery.ui.button.css",
    "~/Content/themes/base/jquery.ui.dialog.css",
    "~/Content/themes/base/jquery.ui.slider.css",
    "~/Content/themes/base/jquery.ui.tabs.css",
    "~/Content/themes/base/jquery.ui.datepicker.css",
    "~/Content/themes/base/jquery.ui.progressbar.css",
    "~/Content/themes/base/jquery.ui.theme.css"));

O método de classe IncludeDirectoryBundle é fornecido para adicionar todos os arquivos em um diretório (e, opcionalmente, todos os subdiretórios) que correspondem a um padrão de pesquisa. A API da classe IncludeDirectoryBundle é mostrada abaixo:

public Bundle IncludeDirectory(
    string directoryVirtualPath,  // The Virtual Path for the directory.
    string searchPattern)         // The search pattern.

public Bundle IncludeDirectory(
    string directoryVirtualPath,  // The Virtual Path for the directory.
    string searchPattern,         // The search pattern.
    bool searchSubdirectories)    // true to search subdirectories.

Os pacotes são referenciados em exibições usando o método Render, (Styles.Render para CSS e Scripts.Render para JavaScript). A marcação a seguir do arquivo Views\Shared\_Layout.cshtml mostra como as exibições padrão do projeto da Internet ASP.NET referenciam pacotes CSS e JavaScript.

<!DOCTYPE html>
<html lang="en">
<head>
    @* Markup removed for clarity.*@    
    @Styles.Render("~/Content/themes/base/css", "~/Content/css")
    @Scripts.Render("~/bundles/modernizr")
</head>
<body>
    @* Markup removed for clarity.*@
   
   @Scripts.Render("~/bundles/jquery")
   @RenderSection("scripts", required: false)
</body>
</html>

Observe que os métodos Render utilizam uma matriz de cadeias de caracteres, para que você possa adicionar vários pacotes em uma linha de código. Geralmente, você desejará usar os métodos Render que criam o HTML necessário para fazer referência ao ativo. Você pode usar o Url método para gerar a URL para o ativo sem a marcação necessária para referenciar o ativo. Suponha que você queira usar o novo atributo assíncrono HTML5. O código a seguir mostra como fazer referência ao modernizr usando o Url método .

<head>
    @*Markup removed for clarity*@
    <meta charset="utf-8" />
    <title>@ViewBag.Title - MVC 4 B/M</title>
    <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
    <meta name="viewport" content="width=device-width" />
    @Styles.Render("~/Content/css")

   @* @Scripts.Render("~/bundles/modernizr")*@

    <script src='@Scripts.Url("~/bundles/modernizr")' async> </script>
</head>

Usando o caractere curinga "*" para selecionar arquivos

O caminho virtual especificado no Include método e o padrão de pesquisa no IncludeDirectory método podem aceitar um caractere curinga "*" como um prefixo ou sufixo para no último segmento de caminho. A cadeia de caracteres de pesquisa não diferencia maiúsculas de minúsculas. O IncludeDirectory método tem a opção de pesquisar subdiretórios.

Considere um projeto com os seguintes arquivos JavaScript:

  • Scripts\Common\AddAltToImg.js
  • Scripts\Common\ToggleDiv.js
  • Scripts\Common\ToggleImg.js
  • Scripts\Common\Sub1\ToggleLinks.js

dir imag

A tabela a seguir mostra os arquivos adicionados a um pacote usando o curinga, conforme mostrado:

Chamar Arquivos adicionados ou exceção gerada
Include("~/Scripts/Common/*.js") AddAltToImg.js, ToggleDiv.js, ToggleImg.js
Include("~/Scripts/Common/T*.js") Exceção de padrão inválida. O caractere curinga só é permitido no prefixo ou sufixo.
Include("~/Scripts/Common/*og.*") Exceção de padrão inválida. Somente um caractere curinga é permitido.
Include("~/Scripts/Common/T*") ToggleDiv.js, ToggleImg.js
Include("~/Scripts/Common/*") Exceção de padrão inválida. Um segmento curinga puro não é válido.
IncludeDirectory("~/Scripts/Common", "T*") ToggleDiv.js, ToggleImg.js
IncludeDirectory("~/Scripts/Common", "T*", true) ToggleDiv.js, ToggleImg.js, ToggleLinks.js

Adicionar explicitamente cada arquivo a um pacote geralmente é o preferido em vez do carregamento curinga de arquivos pelos seguintes motivos:

  • Adicionar scripts por padrões curinga para carregá-los em ordem alfabética, que normalmente não é o que você deseja. Os arquivos CSS e JavaScript frequentemente precisam ser adicionados em uma ordem específica (não alfabética). Você pode atenuar esse risco adicionando uma implementação personalizada do IBundleOrderer , mas adicionar explicitamente cada arquivo é menos propenso a erros. Por exemplo, você pode adicionar novos ativos a uma pasta no futuro, o que pode exigir que você modifique sua implementação do IBundleOrderer .

  • Exibir arquivos específicos adicionados a um diretório usando o carregamento de cartão selvagem pode ser incluído em todos os modos de exibição que fazem referência a esse pacote. Se o script específico da exibição for adicionado a um pacote, você poderá receber um erro JavaScript em outras exibições que referenciam o pacote.

  • Arquivos CSS que importam outros arquivos resultam nos arquivos importados carregados duas vezes. Por exemplo, o código a seguir cria um pacote com a maioria dos arquivos CSS do tema da interface do usuário jQuery carregados duas vezes.

    bundles.Add(new StyleBundle("~/jQueryUI/themes/baseAll")
        .IncludeDirectory("~/Content/themes/base", "*.css"));
    

    O seletor de cartão selvagem "*.css" traz cada arquivo CSS na pasta, incluindo o arquivo Content\themes\base\jquery.ui.all.css. O arquivo jquery.ui.all.css importa outros arquivos CSS.

Cache de pacotes

Os pacotes definem o cabeçalho HTTP Expira um ano a partir de quando o pacote é criado. Se você navegar até uma página exibida anteriormente, o Fiddler mostrará que o IE não faz uma solicitação condicional para o pacote, ou seja, não há solicitações HTTP GET do IE para os pacotes e nenhuma resposta HTTP 304 do servidor. Você pode forçar o IE a fazer uma solicitação condicional para cada pacote com a chave F5 (resultando em uma resposta HTTP 304 para cada pacote). Você pode forçar uma atualização completa usando ^F5 (resultando em uma resposta HTTP 200 para cada pacote.)

A imagem a seguir mostra a guia Cache do painel de resposta fiddler:

imagem de cache do fiddler

A solicitação
http://localhost/MvcBM_time/bundles/AllMyScripts?v=r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81
é para o pacote AllMyScripts e contém um par de cadeias de caracteres de consulta v=r0sLDicvP58AIXN\_mc3QdyVvVj5euZNzdsa2N1PKvb81. A cadeia de caracteres de consulta v tem um token de valor que é um identificador exclusivo usado para cache. Desde que o pacote não seja alterado, o aplicativo ASP.NET solicitará o pacote AllMyScripts usando esse token. Se qualquer arquivo no pacote for alterado, a estrutura de otimização ASP.NET gerará um novo token, garantindo que as solicitações do navegador para o pacote receberão o pacote mais recente.

Se você executar as ferramentas de desenvolvedor do IE9 F12 e navegar até uma página carregada anteriormente, o IE mostrará incorretamente as solicitações GET condicionais feitas a cada pacote e o servidor retornando HTTP 304. Você pode ler por que o IE9 tem problemas para determinar se uma solicitação condicional foi feita na entrada do blog Usando CDNs e expira para melhorar o desempenho do site.

LESS, CoffeeScript, SCSS, Sass Bundling.

A estrutura de agrupamento e minificação fornece um mecanismo para processar linguagens intermediárias, como SCSS, Sass, LESS ou Coffeescript, e aplicar transformações como a minificação ao pacote resultante. Por exemplo, para adicionar arquivos .less ao seu projeto MVC 4:

  1. Crie uma pasta para seu conteúdo LESS. O exemplo a seguir usa a pasta Content\MyLess .

  2. Adicione o pacote NuGet .lesssem ponto ao seu projeto.
    Instalação sem ponto do NuGet

  3. Adicione uma classe que implementa a interface IBundleTransform . Para a transformação .less, adicione o código a seguir ao seu projeto.

    using System.Web.Optimization;
    
    public class LessTransform : IBundleTransform
    {
        public void Process(BundleContext context, BundleResponse response)
        {
            response.Content = dotless.Core.Less.Parse(response.Content);
            response.ContentType = "text/css";
        }
    }
    
  4. Crie um pacote de arquivos LESS com o LessTransform e a transformação CssMinify . Adicione o código a RegisterBundles seguir ao método no arquivo App\_Start\BundleConfig.cs .

    var lessBundle = new Bundle("~/My/Less").IncludeDirectory("~/My", "*.less");
    lessBundle.Transforms.Add(new LessTransform());
    lessBundle.Transforms.Add(new CssMinify());
    bundles.Add(lessBundle);
    
  5. Adicione o código a seguir a quaisquer exibições que referenciem o pacote LESS.

    @Styles.Render("~/My/Less");
    

Considerações sobre pacotes

Uma boa convenção a seguir ao criar pacotes é incluir "bundle" como um prefixo no nome do pacote. Isso impedirá um possível conflito de roteamento.

Depois de atualizar um arquivo em um pacote, um novo token será gerado para o parâmetro de cadeia de caracteres de consulta de pacote e o pacote completo deverá ser baixado na próxima vez que um cliente solicitar uma página que contenha o pacote. Na marcação tradicional em que cada ativo é listado individualmente, somente o arquivo alterado seria baixado. Ativos que mudam com frequência podem não ser bons candidatos ao agrupamento.

O agrupamento e a minificação melhoram principalmente o tempo de carregamento da solicitação da primeira página. Depois que uma página da Web é solicitada, o navegador armazena em cache os ativos (JavaScript, CSS e imagens) para que o agrupamento e a minificação não forneçam nenhum aumento de desempenho ao solicitar a mesma página ou páginas no mesmo site solicitando os mesmos ativos. Se você não definir o cabeçalho expira corretamente em seus ativos e não usar o agrupamento e a minificação, a heurística de atualização dos navegadores marcará os ativos obsoletos após alguns dias e o navegador exigirá uma solicitação de validação para cada ativo. Nesse caso, o agrupamento e a minificação fornecem um aumento de desempenho após a primeira solicitação de página. Para obter detalhes, consulte o blog Usando CDNs e Expira para melhorar o desempenho do site.

A limitação do navegador de seis conexões simultâneas por cada nome de host pode ser atenuada usando uma CDN. Como a CDN terá um nome de host diferente do site de hospedagem, as solicitações de ativo da CDN não contarão com o limite de seis conexões simultâneas para o ambiente de hospedagem. Uma CDN também pode fornecer vantagens comuns de cache de pacote e cache de borda.

Os pacotes devem ser particionados por páginas que precisam deles. Por exemplo, o modelo padrão ASP.NET MVC para um aplicativo da Internet cria um pacote de validação jQuery separado do jQuery. Como as exibições padrão criadas não têm entrada e não postam valores, elas não incluem o pacote de validação.

O System.Web.Optimization namespace é implementado no System.Web.Optimization.dll. Ele aproveita a biblioteca WebGrease (WebGrease.dll) para recursos de minificação, que, por sua vez, usa Antlr3.Runtime.dll.

Uso o Twitter para fazer postagens rápidas e compartilhar links. Meu identificador do Twitter é: @RickAndMSFT

Recursos adicionais

Colaboradores