web-dev-qa-db-ja.com

JavaScriptをオンザフライで連結および縮小するORビルド時に-ASP.NET MVC

ここでこの質問の拡張として ユーザーコントロールでのJavaScriptライブラリのリンク 私は人々がその場でJavaScriptを連結および縮小する方法のいくつかの例を求めていましたORビルド時に。また、それがマスターページにどのように機能するかについても確認したいと思います。

現在のように、ページ固有のファイルが縮小されて個別にリンクされてもかまいません(以下を参照)が、メインマスターページのすべての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>


UPDATE:現在の推奨事項(2013年後半):

Microsoft ASP.NETに組み込まれている Bundling and Minification を見てみます。

46
Charlino

Professional ASP.NET 3.5 の付録では、Scott Hanselmanが Packer for .NET について語っています。これは、MSBuildと統合され、本番環境への展開などのためにJavaScriptファイルをパックします。

7
RedWolves

これを試して:

私は最近、かなりの研究とその結果の開発を完了しました。これは、Webアプリケーションのフロントエンドのパフォーマンスを向上させるためにかなり進んでいます。ここで基本的な解決策を共有したいと思いました。

最初にすべきことは、YahooのYSlowとGoogleのPageSpeedを使用してサイトのベンチマークを行うことです。これらは、「ぶら下がっている果物」のパフォーマンス改善を強調します。まだ行っていない限り、結果の提案にはほぼ確実に静的コンテンツの結合、縮小、gzipが含まれます。

これから実行する手順は次のとおりです。

CSSを組み合わせて縮小するカスタムHTTPHandlerを作成します。 JSを組み合わせて縮小するカスタムHTTPHandlerを作成します。アプリケーションがデバッグモードでない場合にのみ、上記が確実に機能するようにするメカニズムを含めます。 css/jsファイルのインクルードを簡単に維持できるように、カスタムサーバーサイドWebコントロールを記述します。 IIS 6.で特定のコンテンツタイプのGZIPを有効にします。6。さて、.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をコードに設定して、サーバーファーム内のすべてのマシンで同じになるようにします。実際のファイルに応答とキャッシュの依存関係を設定するので、それらが置き換えられた場合、キャッシュは無効になります。プロキシがキャッシュできるようにCacheabilityを設定します。ハンドラーを通じて送信されたCSSファイルグループごとにキャッシュできるように、cssfiles属性を使用してVaryByParamsを作成します。次に、.NET LiteralControlを継承するカスタムサーバー側コントロールであるCSSControlを示します。

フロント:

<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が言うように、WebサイトサーバーでGzipを有効にすることは簡単です。いくつかのトレースを行った後、次のファイルタイプでGzipを有効にすることにしました。

.css .js .axd(Microsoft Javascriptファイル).aspx(通常のASP.NET Webフォームのコンテンツ).ashx(ハンドラー)IIS 6.0 WebサーバーでHTTP圧縮を有効にするには:

IISを開き、[Webサイト]、[サービス]タブを右クリックして、アプリケーションファイルの圧縮と静的ファイルの圧縮を有効にします。停止IIS開くIISメモ帳でメタベース(C:\ WINDOWS\system32\inetsrv\MetaBase.xml)–これらに不安がある場合はバックアップを作成し、2つのIIsCompressionScheme要素と1つのIIsCompressionSchemes要素を見つけて、次のように上書きします。

以上です!これにより大量の帯域幅が節約され、全体的に応答性の高いWebアプリケーションが実現しました。

楽しい!

41
Mark Gibaud

ScriptManagerを使用しないのはなぜですか?これがAND-スキッシュを組み合わせる MVCScriptManager です。

14
Scott Hanselman

YUI CompressorまたはDojoコンプレッサーを使用します。どちらも、コードをトークン化するRhino JS解析エンジンを使用しているため、コードが有効なコードである場合にのみ機能します。エラーがある場合は、通知されます(これは素晴らしいボーナスIMOです)。一方、パッカーは、エラーが含まれている場合でもコードをパックします。

私はすべてのプロジェクトでビルドスクリプトを介してYUIを使用しています。オンザフライで実行しないでください。圧縮に時間がかかりすぎます。 YUIとDojoはどちらもJavaベース(ala Rhino)であり、オンザフライで実行すると、バックグラウンドプロセスを生成して出力を生成することになります。パフォーマンスは良くありません。ビルド時間。

6

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();
4
Sam

CSSとJSファイルを連結、圧縮、キャッシュするために使用したものを以下に示します。 http://Gist.github.com/13091

BinディレクトリにYahoo.Yui.Compressor.dllが必要なだけです。コンパイル時には圧縮されませんが、ファイルはファイル依存関係でキャッシュされるため、変更されるまで一度だけロードされます。

次に、このコードを<head>に追加します。

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

</ body>の直前:

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

すべて同じパスにある複数のファイルを操作するように設計されていますが、さまざまなパスをサポートするように簡単にアップグレードできます。

2
travis

私はお勧めします http://www.RequestReduce.com これは、CSSとJavaScriptを最小化して組み合わせ、CSSの背景画像をスプライトし、PNG圧縮を最適化します。実行時にこれらすべてを実行し、出力をキャッシュします。 HttpModuleを追加する以外にコードや構成は必要ありません。これは、最適化された遠い将来のヘッダーとETagを使用してすべてのキャッシュされたコンテンツを提供し、ブラウザーがcss/javascript/spritesをできるだけ長くキャッシュすることを保証します。構成は必要ありませんが、高度な構成が可能で、CDNを使用して実行し、キャッシュされたファイルをWebファーム間で同期するように設定できます。

すべてのJavaScript、画像、CSSはHTTP経由でフェッチされるため、サードパーティのCSSやJSを含めることができ、WebResource.axdやScriptResource.axdなどの.axdリソースを縮小/結合するための優れた方法でもあります。これは、コンテンツタイプを介してjsおよびcssの存在を決定するため、ターゲットリソースは任意の(またはまったくない)拡張を持つことができます。 MVC、Webフォーム、および「Webページ」のすべてのバージョンとビューエンジンを含む、あらゆるIIS=ベースのテクノロジーで実行されます。

http://www.RequestReduce.comhttps://github.com/mwrock/RequestReduce からNugetまたはforkからダウンロードできます。

2
Matt Wrock

私は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アプリケーションのターゲットファイルが含まれる前に、プロジェクトファイルで設定します。

2
ShadowChaser