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 ao servidor e reduzindo o tamanho dos ativos solicitados (como CSS e JavaScript).
A maioria dos principais navegadores atuais limita o número de conexões simultâneas por cada nome de host a seis. Isso significa que, enquanto seis solicitações estão sendo processadas, solicitações adicionais de 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.
As barras cinzas mostram o tempo em que a solicitação é enfileirada pelo navegador aguardando o limite de seis conexões. A barra amarela é o tempo da solicitação até 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 .
A imagem anterior mostra o evento Start , que fornece o tempo em que a solicitação foi enfileirada porque o limite do navegador é o 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 do 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 do carregamento da primeira página.
A imagem a seguir mostra a mesma exibição de tempo da exibição Sobre mostrada anteriormente, mas desta vez com o agrupamento e a minificação habilitados.
Minificação
A minificação executa uma variedade de otimizações de código diferentes para scripts ou css, como remover espaços em branco e comentários desnecessários e encurtar nomes de variáveis para um caractere. Considere a seguinte função JavaScript.
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 espaços em branco desnecessários, os seguintes parâmetros e nomes de variáveis foram renomeados (encurtados) da seguinte maneira:
Original | Renomeado |
---|---|
imageTagAndImageID | n |
contexto da imagem | t |
elemento de imagem | i |
Impacto do agrupamento e 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 amostra.
Usando B / M | Sem B/M | Altere | |
---|---|---|---|
Solicitações de arquivo | 9 | 34 | 256% |
KB Enviado | 3,26 | 11.92 | 266% |
KB Recebido | 388.51 | 530 | 36% |
Tempo de carregamento | 510 EM | 780 EM | 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 aplicam nas solicitações. A redução de bytes recebidos não é tão grande porque os arquivos maiores (Scripts\jquery-ui-1.8.11.min.js e Scripts\jquery-1.7.1.min.js) já estão minificados. Observação: os tempos no programa de amostra usaram a ferramenta Fiddler para simular uma rede lenta. (Do violinista Regras , selecione Desempenho e, em seguida , Simular velocidades do modem.)
Depuração de JavaScript empacotado e minificado
É fácil depurar o JavaScript em um ambiente de desenvolvimento (em que o elemento de compilação no arquivo Web.config é definido como debug="true"
) porque os arquivos JavaScript não são agrupados ou reduzidos. Você também pode depurar uma compilação de versão em que seus arquivos JavaScript são agrupados e reduzidos. Usando as ferramentas de desenvolvedor do IE F12, você depura uma função JavaScript incluída em um pacote minificado usando a seguinte abordagem:
- Selecione a guia Script e, em seguida, selecione o botão Iniciar depuração .
- Selecione o pacote que contém a função JavaScript que você deseja depurar usando o botão de ativos.
- Formate o JavaScript reduzido selecionando o botão Configuração e, em seguida, selecionando Formatar JavaScript.
- Na caixa de entrada Script de Pesquisa, selecione o nome da função que você deseja depurar. Na imagem a seguir, AddAltToImg foi inserido na caixa de entrada Script de Pesquisa .
Para obter mais informações sobre a depuração com as ferramentas de desenvolvedor F12, consulte o artigo do MSDN Usando as ferramentas de desenvolvedor 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 debug no elemento de compilação no arquivo Web.config . No XML a seguir, é definido como true, portanto, debug
o agrupamento e a minificação são desativados.
<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 pela 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
A menos que EnableOptimizations
seja true
ou o atributo debug no elemento de compilação no arquivo Web.config esteja definido como false
, os arquivos não serão agrupados ou reduzidos. Além disso, a versão .min dos arquivos não será usada, as versões completas de depuração serão selecionadas. EnableOptimizations
substitui o atributo debug no elemento de compilação no arquivo Web.config
Usando agrupamento e minificação com ASP.NET Web Forms e páginas da Web
- Para Páginas da Web, consulte a entrada de blog Adicionando Otimização da Web a um Site de Páginas da Web.
- Para Web Forms, consulte a entrada do blog Adicionando agrupamento e minificação a formulários 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 (ou seja, depuração ou minificado, mas não .vsdoc) na pasta Scripts que correspondem à cadeia de caracteres curinga "~/Scripts/jquery-{version}.js". Para ASP.NET MVC 4, isso significa que, com uma configuração de depuração, o arquivo jquery-1.7.1.js será adicionado ao pacote. Em uma configuração de versão, jquery-1.7.1.min.js serão adicionados. A estrutura de agrupamento segue várias convenções comuns, como:
- Selecionando o arquivo ".min" para liberação quando FileX.min.js e FileX.js existirem.
- Selecionando a versão não ".min" para depuração.
- Ignorar arquivos "-vsdoc" (como jquery-1.7.1-vsdoc.js), que são usados apenas pelo IntelliSense.
A {version}
correspondência de curinga mostrada acima é usada para criar automaticamente um pacote jQuery com a versão apropriada do jQuery na pasta Scripts . Neste exemplo, o uso de um curinga 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 do 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 compilações de versão.
Usando um CDN
O código a seguir substitui o pacote jQuery local por um pacote jQuery 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, o jQuery será solicitado da CDN enquanto estiver no modo de lançamento 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 da CDN falhe. O fragmento de marcação a seguir no 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 Include
Bundle usa uma matriz de cadeias de caracteres, em que cada cadeia de caracteres é um caminho virtual para o recurso. O código a seguir 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 da classe IncludeDirectory
Bundle é 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 IncludeDirectory
Bundle é 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 ASP.NET Internet fazem referência a 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 usam 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 o URL para o ativo sem a marcação necessária para fazer referência ao ativo. Suponha que você queira usar o novo atributo assíncrono HTML5. O código a seguir mostra como referenciar o 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 método podem aceitar um caractere curinga IncludeDirectory
"*" como prefixo ou sufixo 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
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. Apenas 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 |
A adição explícita de cada arquivo a um pacote geralmente é a preferida em relação ao carregamento curinga de arquivos pelos seguintes motivos:
Adicionar scripts por curinga é padrão para carregá-los em ordem alfabética, o 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 IBundleOrderer personalizada, 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 IBundleOrderer .
Exibir arquivos específicos adicionados a um diretório usando o carregamento de curinga pode ser incluído em todas as exibições que fazem referência a esse pacote. Se o script específico da visualização for adicionado a um pacote, você poderá receber um erro de JavaScript em outras exibições que fazem referência ao pacote.
Os 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 do jQuery carregados duas vezes.
bundles.Add(new StyleBundle("~/jQueryUI/themes/baseAll") .IncludeDirectory("~/Content/themes/base", "*.css"));
O seletor curinga "*.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 pacote
Os pacotes configuráveis definem o cabeçalho HTTP Expires um ano a partir do momento em que o pacote é criado. Se você navegar para 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 do Fiddler:
A solicitação
http://localhost/MvcBM_time/bundles/AllMyScripts?v=r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81
é para o pacote AllMyScripts e contém um par de cadeia 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 armazenamento em cache. Desde que o pacote não seja alterado, o aplicativo ASP.NET solicitará o pacote AllMyScripts usando esse token. Se algum 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 obtenham o pacote mais recente.
Se você executar as ferramentas de desenvolvedor F12 do IE9 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 de blog Usando CDNs e expira para melhorar o desempenho do site.
LESS, CoffeeScript, SCSS, Sass Bundling.
A estrutura de empacotamento e minificação fornece um mecanismo para processar linguagens intermediárias, como SCSS, Sass, LESS ou Coffeescript, e aplicar transformações, como minificação, ao pacote configurável resultante. Por exemplo, para adicionar arquivos .less ao seu projeto MVC 4:
Crie uma pasta para o seu conteúdo LESS. O exemplo a seguir usa a pasta Content\MyLess .
Adicione o pacote NuGet .less dotless ao seu projeto.
Adicione uma classe que implemente 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"; } }
Crie um pacote de arquivos LESS com a
LessTransform
transformação e CssMinify. Adicione o código aRegisterBundles
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);
Adicione o código a seguir a todas as exibições que fazem referência ao pacote LESS.
@Styles.Render("~/My/Less");
Considerações sobre o pacote
Uma boa convenção a seguir ao criar pacotes configuráveis é incluir "pacote" como um prefixo no nome do pacote. Isso evitará um possível conflito de roteamento.
Depois de atualizar um arquivo em um pacote, um novo token é gerado para o parâmetro de cadeia de caracteres de consulta do pacote e o pacote completo deve 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, apenas o arquivo alterado seria baixado. Ativos que mudam com frequência podem não ser bons candidatos para 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 expires corretamente em seus ativos e não usar agrupamento e minificação, a heurística de atualização do navegador 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 solicitação da primeira 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 um CDN. Como a CDN terá um nome de host diferente do seu site de hospedagem, as solicitações de ativos da CDN não contarão para o limite de seis conexões simultâneas para seu ambiente de hospedagem. Um CDN também pode fornecer vantagens comuns de cache de pacotes e cache de borda.
Os pacotes devem ser particionados por páginas que precisam deles. Por exemplo, o modelo MVC ASP.NET padrão para um aplicativo da Internet cria um pacote de validação do 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 em System.Web.Optimization.dll. Ele aproveita a biblioteca WebGrease (WebGrease.dll) para recursos de minificação, que por sua vez usa Antlr3.Runtime.dll.
Eu uso o Twitter para fazer postagens rápidas e compartilhar links. Meu identificador do Twitter é: @RickAndMSFT
Recursos adicionais
- Vídeo:Empacotamento e otimização por Howard Dierking
- Adicionando otimização da Web a um site de páginas da Web.
- Adicionando agrupamento e minificação a formulários da Web.
- Implicações de desempenho do agrupamento e minificação na navegação na Web por Henrik F Nielsen @frystyk
- Usando CDNs e expira para melhorar o desempenho do site por Rick Anderson @RickAndMSFT
- Minimize o RTT (tempos de ida e volta)
Colaboradores
- Hao Kung
- Howard Dierking
- Diana LaRose