在运行时或在构建时连接和缩小JavaScript - ASP.NET MVC

时间:2009-05-20 22:21:34

标签: c# javascript css asp.net-mvc minify

作为这个问题的扩展Linking JavaScript Libraries in User Controls我在一些例子中说明了人们如何在运行时或在构建时连接和缩小JavaScript。我还想看看它如何在你的母版页中起作用。

我不介意页面特定的文件被缩小并按照它们当前的单独链接(见下文),但是主母版页上的所有JavaScript文件(我有大约5或6个)我想连接和缩小。< / p>

任何同时融入CSS连接和缩小的人的加分! : - )

当前母版页包含我想要连接和缩小的常见JavaScript文件:

<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>
<head runat="server">
    ... BLAH ...
    <asp:ContentPlaceHolder ID="AdditionalHead" runat="server" />
    ... BLAH ...
    <%= Html.CSSBlock("/styles/site.css") %>
    <%= Html.CSSBlock("/styles/jquery-ui-1.7.1.css") %>
    <%= Html.CSSBlock("/styles/jquery.lightbox-0.5.css") %>
    <%= Html.CSSBlock("/styles/ie6.css", 6) %>
    <%= Html.CSSBlock("/styles/ie7.css", 7) %>
    <asp:ContentPlaceHolder ID="AdditionalCSS" runat="server" />
</head>
<body>
    ... BLAH ...
    <%= Html.JSBlock("/scripts/jquery-1.3.2.js", "/scripts/jquery-1.3.2.min.js") %>
    <%= Html.JSBlock("/scripts/jquery-ui-1.7.1.js", "/scripts/jquery-ui-1.7.1.min.js") %>
    <%= Html.JSBlock("/scripts/jquery.validate.js", "/scripts/jquery.validate.min.js") %>
    <%= Html.JSBlock("/scripts/jquery.lightbox-0.5.js", "/scripts/jquery.lightbox-0.5.min.js") %>
    <%= Html.JSBlock("/scripts/global.js", "/scripts/global.min.js") %>
    <asp:ContentPlaceHolder ID="AdditionalJS" runat="server" />
</body>

在这样的页面中使用(我很满意):

<asp:Content ID="signUpContent" ContentPlaceHolderID="AdditionalJS" runat="server">
    <%= Html.JSBlock("/scripts/pages/account.signup.js", "/scripts/pages/account.signup.min.js") %>
</asp:Content>


更新:目前的建议(2013年底):

我会看一下内置Bundling and Minification的Microsoft ASP.NET。

8 个答案:

答案 0 :(得分:41)

试试这个:

我最近在工作中完成了相当多的研究和后续开发,这对于提高我们的Web应用程序前端的性能起到了很大的作用。我以为我会在这里分享基本解决方案。

第一个显而易见的事情是使用Yahoo的YSlow和Google的PageSpeed对您的网站进行基准测试。这些将突出“低悬的水果”性能改进。除非您已经这样做,否则最终的建议几乎肯定会包括组合,缩小和压缩静态内容。

我们要执行的步骤是:

编写自定义HTTPHandler以组合和缩小CSS。 编写自定义HTTPHandler来组合和缩小JS。 包含一种机制,以确保上述仅在应用程序未处于调试模式时才能发挥作用。 编写自定义服务器端Web控件以轻松维护css / js文件包含。 在IIS 6上启用某些内容类型的GZIP。 是的,让我们从实现.NET IHttpHandler接口的CSSHandler.asax开始:

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Web;

namespace WebApplication1
{
    public class CssHandler : IHttpHandler
    {
        public bool IsReusable { get { return true; } }

        public void ProcessRequest(HttpContext context)
        {
            string[] cssFiles = context.Request.QueryString["cssfiles"].Split(',');

            List<string> files = new List<string>();
            StringBuilder response = new StringBuilder();
            foreach (string cssFile in cssFiles)
            {
                if (!cssFile.EndsWith(".css", StringComparison.OrdinalIgnoreCase))
                {
                    //log custom exception
                    context.Response.StatusCode = 403;
                    return;
                }

                try
                {
                    string filePath = context.Server.MapPath(cssFile);
                    string css = File.ReadAllText(filePath);
                    string compressedCss = Yahoo.Yui.Compressor.CssCompressor.Compress(css);
                    response.Append(compressedCss);
                }
                catch (Exception ex)
                {
                    //log exception
                    context.Response.StatusCode = 500;
                    return;
                }
            }

            context.Response.Write(response.ToString());

            string version = "1.0"; //your dynamic version number 

            context.Response.ContentType = "text/css";
            context.Response.AddFileDependencies(files.ToArray());
            HttpCachePolicy cache = context.Response.Cache;
            cache.SetCacheability(HttpCacheability.Public);
            cache.VaryByParams["cssfiles"] = true;
            cache.SetETag(version);
            cache.SetLastModifiedFromFileDependencies();
            cache.SetMaxAge(TimeSpan.FromDays(14));
            cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
        }
    }
}

好的,现在有一些解释:

IsReUsable属性:

我们没有处理任何特定于实例的事情,这意味着我们可以安全地重用处理程序的同一个实例来处理多个请求,因为我们的ProcessRequest是线程安全的。更多信息。

ProcessRequest方法:

这里没有太忙乱了。在将内容添加到传出响应流之前,我们循环遍历给我们的CSS文件(请参阅下面的CSSControl,了解它们是如何进入的)并使用Yahoo的YUICompressor的.NET端口压缩每个文件。

该方法的其余部分涉及设置一些HTTP缓存属性,以进一步优化浏览器客户端下载(或不根据情况下)内容的方式。

我们在代码中设置了Etags,以便它们可以在我们的服务器场中的所有计算机上相同。 我们在实际文件上设置Response和Cache依赖关系,因此,如果替换它们,缓存将失效。 我们设置Cacheability,以便代理可以缓存。 我们使用我们的cssfiles属性VaryByParams,这样我们就可以缓存每个通过处理程序提交的CSS文件组。 这是CSSControl,一个继承.NET LiteralControl的自定义服务器端控件。

前:

<customcontrols:csscontrol id="cssControl" runat="server">
  <CustomControls:Stylesheet File="main.css" />
  <CustomControls:Stylesheet File="layout.css" />
  <CustomControls:Stylesheet File="formatting.css" />
</customcontrols:csscontrol>

备份:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Web;
using System.Web.UI;
using System.Linq;
using TTC.iTropics.Utilities;

namespace WebApplication1
{
    [DefaultProperty("Stylesheets")]
    [ParseChildren(true, "Stylesheets")]
    public class CssControl : LiteralControl
    {
        [PersistenceMode(PersistenceMode.InnerDefaultProperty)]
        public List<Stylesheet> Stylesheets { get; set; }

        public CssControl()
        {
            Stylesheets = new List<Stylesheet>();
        }

        protected override void Render(HtmlTextWriter output)
        {
            if (HttpContext.Current.IsDebuggingEnabled)
            {
                const string format = "<link rel=\"Stylesheet\" href=\"stylesheets/{0}\"></link>";

                foreach (Stylesheet sheet in Stylesheets)
                    output.Write(format, sheet.File);
            }
            else
            {
                const string format = "<link type=\"text/css\" rel=\"Stylesheet\" href=\"stylesheets/CssHandler.ashx?cssfiles={0}&version={1}\"/>";
                IEnumerable<string> stylesheetsArray = Stylesheets.Select(s => s.File);
                string stylesheets = String.Join(",", stylesheetsArray.ToArray());
                string version = "1.00" //your version number

                output.Write(format, stylesheets, version);
            }

        }
    }

    public class Stylesheet
    {
        public string File { get; set; }
    }
}

HttpContext.Current.IsDebuggingEnabled连接到web.config中的以下设置:

<system.web>
  <compilation debug="false">
</system.web>

因此,基本上,如果您的站点处于调试模式,您将获得如下HTML标记:

<link rel="Stylesheet" href="stylesheets/formatting.css"></link>
<link rel="Stylesheet" href="stylesheets/layout.css"></link
<link rel="Stylesheet" href="stylesheets/main.css"></link>

但是如果你处于生产模式(debug = false),你会得到这样的标记:

<link type="text/css" rel="Stylesheet" href="CssHandler.ashx?cssfiles=main.css,layout.css,formatting.css&version=1.0"/>

后者显然会调用CSSHandler,它将负责组合,缩小和缓存读取静态CSS内容。

以上所有内容也可以复制到您的静态JavaScript内容中:

`JSHandler.ashx:

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Web;

namespace WebApplication1
{
    public class JSHandler : IHttpHandler
    {
        public bool IsReusable { get { return true; } }

        public void ProcessRequest(HttpContext context)
        {
            string[] jsFiles = context.Request.QueryString["jsfiles"].Split(',');

            List<string> files = new List<string>();
            StringBuilder response = new StringBuilder();

            foreach (string jsFile in jsFiles)
            {
                if (!jsFile.EndsWith(".js", StringComparison.OrdinalIgnoreCase))
                {
                    //log custom exception
                    context.Response.StatusCode = 403;
                    return;
                }

                try
                {
                    string filePath = context.Server.MapPath(jsFile);
                    files.Add(filePath);
                    string js = File.ReadAllText(filePath);
                    string compressedJS = Yahoo.Yui.Compressor.JavaScriptCompressor.Compress(js);
                    response.Append(compressedJS);
                }
                catch (Exception ex)
                {
                    //log exception
                    context.Response.StatusCode = 500;
                    return;
                }
            }

            context.Response.Write(response.ToString());

            string version = "1.0"; //your dynamic version number here

            context.Response.ContentType = "application/javascript";
            context.Response.AddFileDependencies(files.ToArray());
            HttpCachePolicy cache = context.Response.Cache;
            cache.SetCacheability(HttpCacheability.Public);
            cache.VaryByParams["jsfiles"] = true;
            cache.VaryByParams["version"] = true;
            cache.SetETag(version);
            cache.SetLastModifiedFromFileDependencies();
            cache.SetMaxAge(TimeSpan.FromDays(14));
            cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
        }
    }
}

及其随附的JSControl:

前:

<customcontrols:JSControl ID="jsControl" runat="server">
  <customcontrols:Script File="jquery/jquery-1.3.2.js" />
  <customcontrols:Script File="main.js" />
  <customcontrols:Script File="creditcardpayments.js" />
</customcontrols:JSControl>

备份:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Web;
using System.Web.UI;
using System.Linq;

namespace WebApplication1
{
    [DefaultProperty("Scripts")]
    [ParseChildren(true, "Scripts")]
    public class JSControl : LiteralControl
    {
        [PersistenceMode(PersistenceMode.InnerDefaultProperty)]
        public List<Script> Scripts { get; set; }

        public JSControl()
        {
            Scripts = new List<Script>();
        }

        protected override void Render(HtmlTextWriter writer)
        {
            if (HttpContext.Current.IsDebuggingEnabled)
            {
                const string format = "<script src=\"scripts\\{0}\"></script>";

                foreach (Script script in Scripts)
                    writer.Write(format, script.File);
            }
            else
            {
                IEnumerable<string> scriptsArray = Scripts.Select(s => s.File);
                string scripts = String.Join(",", scriptsArray.ToArray());
                string version = "1.0" //your dynamic version number
                const string format = "<script src=\"scripts/JsHandler.ashx?jsfiles={0}&version={1}\"></script>";

                writer.Write(format, scripts, version);
            }
        }
    }

    public class Script
    {
        public string File { get; set; }
    }
}

启用GZIP:

正如Jeff Atwood所说,在您的网站服务器上启用Gzip是一个明智的选择。经过一些跟踪,我决定在以下文件类型上启用Gzip:

的CSS .js文件 .axd(Microsoft Javascript文件) .aspx(通常的ASP.NET Web窗体内容) .ashx(我们的处理程序) 在IIS 6.0 Web服务器上启用HTTP压缩:

打开IIS,右键单击“网站”,“服务”选项卡,启用“压缩应用程序文件”和“压缩静态文件” 停止IIS 在记事本中打开IIS Metabase(C:\ WINDOWS \ system32 \ inetsrv \ MetaBase.xml) - 如果你对这些东西感到紧张,请进行备份 使用以下命令找到并覆盖两个IIsCompressionScheme和一个IIsCompressionSchemes元素:                         

就是这样!这为我们节省了大量带宽,并在整个过程中提供了更具响应性的Web应用程序。

享受!

答案 1 :(得分:14)

为什么不使用ScriptManager?这是MVCScriptManager,它将结合并挤压。

答案 2 :(得分:7)

Professional ASP.NET 3.5的附录中,Scott Hanselman谈到Packer for .NET。这将与MSBuild集成并打包javascript文件以进行生产部署等。

答案 3 :(得分:6)

使用YUI Compressor或Dojo压缩器。它们都使用Rhino JS解析引擎来代码化你的代码,因此只有在代码是有效代码时才能工作。如果有错误,他们会让你知道(这是一个很好的奖励IMO!)另一方面,Packer将包装你的代码,即使它包含错误。

我通过构建脚本在我的所有项目中使用YUI。永远不要在飞行中进行,压缩需要很长时间。 YUI和Dojo都是基于Java的(ala Rhino),如果你动手做,你将产生后台进程来生成输出 - 不利于性能。始终在构建时进行。

答案 4 :(得分:4)

Rejuicer是ASP.NET的一个很棒的新增功能器,它引起了很多关注: http://rejuice.me

它被配置为HTTP模块&amp;在运行时执行缩小(一次)并缓存输出。

有:

  • 具有流畅的配置界面
  • 允许您指定要使用通配符规则缩小的文件
  • 在Windows Azure上运行
  • 在开发环境中有些神奇地将自己关闭,因此您可以调试原始的javascript代码(不会缩小)。

配置(在global.asax.cs中的ApplicationStart上完成)非常简单:

OnRequest.ForJs("~/Combined.js")
            .Compact
            .FilesIn("~/Scripts/")
              .Matching("*.js")
            .Cache
            .Configure();

答案 5 :(得分:2)

这是我用于连接,压缩和缓存CSS和JS文件的内容: http://gist.github.com/130913

它只需要bin目录中的Yahoo.Yui.Compressor.dll。它不会在编译时压缩,但文件缓存时会出现文件依赖性,所以它们只加载一次,直到它们被更改为止。

然后我只需在&lt; head&gt;中添加此代码:

<link rel="stylesheet" type="text/css" href="/YuiCompressor.ashx?css=reset,style,etc" />

这就在&lt; / body&gt;之前:

<script type="text/javascript" src="/YuiCompressor.ashx?js=main,other,etc"></script>

它设计用于处理同一路径中的多个文件,但可以轻松升级以支持不同的路径。

答案 6 :(得分:2)

我建议使用http://www.RequestReduce.com来最小化和组合css和javascript以及sprite css背景图像并优化其PNG压缩。它在运行时完成所有这些并缓存输出。除了添加HttpModule之外,它不需要任何代码或配置。它为所有缓存内容提供优化的远期标头和ETag,以确保浏览器尽可能长时间地缓存css / javascript / sprites。虽然它不需要配置,但它具有高度可配置性,可以设置为在Web场中使用CDN和同步缓存文件运行。

所有javascript,images和css都是通过HTTP获取的,因此它可以包含来自第三方的css和js,它也是缩小/组合.axd资源(如WebResource.axd和ScriptResource.axd)的好方法。它通过内容类型确定js和css的存在,因此目标资源可以具有任何(或没有)扩展。它运行在任何基于IIS的技术上,包括MVC的所有版本和视图引擎,Web表单和“网页”。

您可以从http://www.RequestReduce.com下载,Nuget或从https://github.com/mwrock/RequestReduce下载。

答案 7 :(得分:2)

我使用基于MSBuild和Microsoft Ajax Minifier的自定义解决方案。许多现有的博客文章都没有正确处理某些案例,例如与TFS构建集成。

对于每个Web项目,我们创建一个“wpp.targets”文件来扩展Web Publishing Pipeline。例如,如果项目是“Website.csproj”,则在项目中创建名为“Website.wpp.targets”的文件。

将以下代码放在目标文件中:

<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath32)\PATH TO YOUR MSBUILD MINIFY TARGETS" />

  <!-- Hook up minification task to WPP build process -->
  <PropertyGroup>
    <OnAfterPipelineTransformPhase>
      $(OnAfterPipelineTransformPhase);
      MinifyResourceFiles;
    </OnAfterPipelineTransformPhase>
  </PropertyGroup>

  <!-- Define temporary location to store minified resources -->
  <PropertyGroup>
    <MinifyResourceIntermediateOutput Condition="'$(MinifyResourceIntermediateOutput)'==''">MinifyResourceFiles</MinifyResourceIntermediateOutput>
    <MinifyResourceIntermediateLocation Condition="'$(MinifyResourceIntermediateLocation)'==''">$(_WPPDefaultIntermediateOutputPath)$(MinifyResourceIntermediateOutput)</MinifyResourceIntermediateLocation>
  </PropertyGroup>

  <Target Name="MinifyResourceFiles" DependsOnTargets="PipelineCollectFilesPhase" Condition="'$(Configuration)' == 'Release'">
    <!-- Create lists of the resources to minify -->
    <!-- These extract all Javascript and CSS files from the publishing pipeline "FilesForPackagingFromProject" and create two new lists.
     The "MinifiedFile" metadata on each item contains the temporary location where the minified file will be stored -->
    <ItemGroup>
      <JavaScriptToMinify Include="@(FilesForPackagingFromProject)" 
                          Condition="'%(FilesForPackagingFromProject.Extension)' == '.js'">
        <MinifiedFile>$(MinifyResourceIntermediateLocation)\minified\%(DestinationRelativePath)</MinifiedFile>
      </JavaScriptToMinify>
      <StylesheetToMinify Include="@(FilesForPackagingFromProject)"
                          Condition="'%(FilesForPackagingFromProject.Extension)' == '.css'">
        <MinifiedFile>$(MinifyResourceIntermediateLocation)\minified\%(DestinationRelativePath)</MinifiedFile>
      </StylesheetToMinify>    
    </ItemGroup>

    <!-- Minify resources -->
    <!-- These commands should be replaced with the MSBuild Tasks used to perform your minification
         I use my own custom tasks based on the Microsoft Ajax Minifier DLL 
         The input of the minifier takes a source file directly from the project and outputs to a temporary location -->
    <MinifyJavaScript SourceFiles="@(JavaScriptToMinify)" DestinationFiles="@(JavaScriptToMinify->'%(MinifiedFile)')"
                      Comments="None" />
    <MinifyStylesheet SourceFiles="@(StylesheetToMinify)" DestinationFiles="@(StylesheetToMinify->'%(MinifiedFile)')"
                      Comments="None" />

    <!-- Remove the original source files from the packaging system and include the new minfied resources from the temporary location -->
    <ItemGroup>
      <!--Remove unminified resources from the pipeline -->
      <FilesForPackagingFromProject Remove="@(JavaScriptToMinify)" Condition="'@(JavaScriptToMinify)' != ''" />
      <FilesForPackagingFromProject Remove="@(StylesheetToMinify)" Condition="'@(StylesheetToMinify)' != ''" />
      <!--Add the minified resources at the new loction to the pipeline -->
      <FilesForPackagingFromProject Include="@(JavaScriptToMinify->'%(MinifiedFile)')" Condition="'@(JavaScriptToMinify)' != ''"/>
      <FilesForPackagingFromProject Include="@(StylesheetToMinify->'%(MinifiedFile)')" Condition="'@(StylesheetToMinify)' != ''"/>
    </ItemGroup>
  </Target>
</Project>

根据您的需要,可以修改缩小目标上的“'$(配置')=='释放'”条件。在服务器上发布,打包和构建时,它将自动缩小(并验证)项目中的所有CSS和JS文件。

您可能需要为服务器构建启用WPP“CopyWebApplication”目标。为此,请将MSBuild属性UseWP_CopyWebApplication设置为True,将PipelineDependsOnBuild设置为False。我们在包含Web应用程序目标文件之前在项目文件中设置它们。