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

Posted

技术标签:

【中文标题】即时或在构建时连接和缩小 JavaScript - ASP.NET MVC【英文标题】:Concatenate and minify JavaScript on the fly OR at build time - ASP.NET MVC 【发布时间】:2010-10-27 19:04:29 【问题描述】:

作为对Linking javascript Libraries in User Controls 此处问题的扩展,我关注了一些关于人们如何在运行中或在构建时连接和缩小 JavaScript 的示例。我还想看看它如何在您的母版页中发挥作用。

我不介意页面特定文件像当前那样被单独缩小和链接(见下文),但主母版页上的所有 JavaScript 文件(我有大约 5 或 6 个)我希望连接和缩小。

任何还包含 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 年末):

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

【问题讨论】:

很想看看人们在这里做什么。 YUI compress 的端口看起来是最好的起点。 谁有使用 YUI 的解决方案? duck's nuts 是好是坏? 很好 :-) 虽然这个答案现在可能已经过时了......那里有更好的傻瓜。 我要问“鸭子的坚果”是否可以接受... 【参考方案1】:

试试这个:

我最近在工作中完成了相当多的研究和后续开发工作,这对提高我们的 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 缓存属性,以进一步优化浏览器客户端下载(或不下载,视情况而定)内容的方式。

我们在代码中设置了 Etag,以便它们在我们服务器场中的所有机器上都相同。 我们在实际文件上设置了响应和缓存依赖项,因此,如果它们被替换,缓存将失效。 我们设置 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 元数据库 (C:\WINDOWS\system32\inetsrv\MetaBase.xml) – 如果您对这些事情感到紧张,请进行备份 使用以下内容找到并覆盖两个 IIsCompressionScheme 和一个 IIsCompressionSchemes 元素:

就是这样!这为我们节省了大量带宽,并在整个过程中产生了一个响应速度更快的 Web 应用程序。

享受吧!

【讨论】:

哇 - 这是一个非常详细的回复,绝对值得在某个地方发表博客文章!如果它适合您的网站,绝对是一个很好的解决方案。在我的网站上,所有需要组合的 js 和 css 无论如何都会组合起来,所以我真的不需要这么复杂的解决方案。是的,我启用了 gzip。另外,我已经将我的 js 和 css 文件的未来过期标头和自动版本控制放在一个 cookie 免费域上 - 但这是另一个问题! 几年后,世界已经发生了变化,尽管我需要在我的新雇主处重新解决这个问题。毫无疑问,我现在建议使用 Cassette:getcassette.net【参考方案2】:

为什么不使用 ScriptManager?这是一个MVCScriptManager,它将结合 AND squish。

【讨论】:

这看起来是动态连接和缩小的绝佳选择。但我肯定会倾向于构建时间解决方案。没有开销就更干净了,而且我可以在那里做 CSS :-)【参考方案3】:

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

【讨论】:

看起来不错,我得试一试。我听说过有关“Packer”的坏消息,但我发现它也支持“JSMin”。 虽然看起来不错,但 YUI Compress 的一个优势似乎是它还可以进行 CSS 压缩和连接。 Packer for .NET 确实可以进行 CSS 连接和缩小 - 查看链接 :-) 但是,我确实听说 YUI Compress 在缩小 JS 和 CSS 方面做得比其他任何东西都好。 【参考方案4】:

使用 YUI Compressor 或 Dojo 压缩器。它们都使用 Rhino JS 解析引擎来标记您的代码,因此只有在代码是有效代码时才能工作。如果出现错误,他们会通知您(这是 IMO 的一个不错的奖励!)另一方面,Packer 会打包您的代码,即使它包含错误。

我通过构建脚本在所有项目中使用 YUI。永远不要在飞行中进行,压缩时间太长了。 YUI 和 Dojo 都是基于 Java 的(ala Rhino),如果您在运行中执行此操作,您将生成后台进程来生成输出 - 不利于性能。始终在构建时进行。

【讨论】:

【参考方案5】:

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

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

它:

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

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

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

【讨论】:

【参考方案6】:

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

它只需要 bin 目录中的 Yahoo.Yui.Compressor.dll。它不会在编译时压缩,但文件会通过文件依赖项进行缓存,因此它们只会加载一次,直到它们被更改为止。

然后我只是在

中添加这段代码:
<link rel="stylesheet" type="text/css" href="/YuiCompressor.ashx?css=reset,style,etc" />

这个就在之前:

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

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

【讨论】:

【参考方案7】:

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

对于每个 Web 项目,我们创建一个“wpp.targets”文件来扩展 Web 发布管道。例如,如果项目是“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>

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

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

【讨论】:

【参考方案8】:

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

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

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

【讨论】:

以上是关于即时或在构建时连接和缩小 JavaScript - ASP.NET MVC的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Grunt.js (0.3.x) 连接和缩小多个 CSS 和 JavaScript 文件

创建两列响应式 css html 设计,当缩小或在移动设备上查看时合并为 1 列

有没有好的 JavaScript 缩小器? [关闭]

缩小帽存储类型

使用 jQuery 即时构建 HTML 表格

在 JavaScript 中使用媒体查询或在 CSS 中使用 if/else 语句