Bundling و Minification در ASP.Net

چهارشنبه 26 آبان 1395

Bundling و Minification دو تکنیکی هستند که در ASP.Net شما میتوانید از آنها برای بهبود زمان بارگذاری درخواست صفحه استفاده کنید . Bundling و Minification این بهبود زمان را بوسیله ی کاهش تعداد درخواست های ارسالی به سمت سرور و کاهش سایز و اندازه فایل های موجود (فایل های Javascript و CSS ) ، انجام میدهد . در این مقاله ، با مفهوم Bundling و Minification آشنا خواهید شد .

Bundling و Minification در ASP.Net

بیشتر مرورگرهای بزرگ امروزی تعداد simultaneous connections را به ازای هر Hostname محدود به 6 تا کردند ، این بدان معناست که زمانی که شش درخواست در حال پردازش می‌باشند ، اگر درخواست جدیدی بیاید ، توسط مرورگر درون صف قرار میگیرد . عکس زیر مربوط به مرورگر IE می‌باشد :



نوار های طوسی بدان معناست که درخواست توسط مرورگر در صف قرار گرفته است ، نوار زرد نشان دهنده بازه زمانی است از دریافت اولین بایت از درخواست تا زمانی که اول بایت از پاسخ ارسال شود . نوار آبی نیز نشان دهنده مدت زمان دریافت پاسخ از سرور است . برای مشاهده جزئیات زمانبندی شما میتوانید روی آنها double-click کنید . برای مثال تصویر زیر نشان دهنده زمان بندی برای فایل Scripts/MyScripts/JavaScript6.js/ می‌باشد . 




تصویر قبلی نشان دهنده رویداد Start است ، که درخواست به دلیل محدودیت تعداد simultaneous connections مرورگر وارد صف شده است . در این مورد ، این درخواست به مدت 46 میلی ثانیه ، برای کامل شدن درخواست های دیگر منتظر مانده است . 

Bundling :

Bundling ویژگی جدیدی است که با ASP.Net 4.5 ارائه شد .  آن چندین فایل را به آسانی در قالب یک فایل ترکیب و دسته بندی میکند . شما میتوانید bundle های مختلفی برای فایل های CSS و JavaScript بسازید . وجود فایل کمتر به معنای درخواست HTTP کمتر میباشد که همین امر باعث بهبود بارگذاری صفحه می‌شود . 
تصویر زیر زمانبندی About View که در مثال قبلی بررسی شد را همراه با Bundling و  Minification نشان میدهد . 





Minification :

Minification کارهای مختلفی را برای بهینه سازی Scriptها و CSS ها انجام میدهد ، نظیر پاک کردن فضاهای خالی و space های بین کدها ، حذف کامنت ها ، کوتاه کردن نام متغیرها و تبدیل آن به نام یک کاراکتری . کد 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/, ''));
}


بعد از minification ، کد بصورت زیر خواهد شد :

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

علاوه بر پاک کردن کامنت ها و فضاهای خالی ، نام متغیرها نیز تغییر کرده و کوتاه شده است : 

imageTagAndImageID --> n
imageContext --> t
imageElement --> i

تاثیر Bundling  و Minification :

جدول زیر چندین تفاوت مهم و اساسی بین زمانی که فایلها را بصورت انفرادی ارسال میکردیم و زمانی که از Bundling و Minification استفاده میکنیم را نشان میدهد : 




بایت های ارسالی بوسیله bundling کاهش چشمگیری دارند ، اما میزان  کاهش بایت های دریافتی زیاد نیست ، به این دلیل که بزرگترین فایل های آن (Scripts\jquery-ui-1.8.11.min.js و  Scripts\jquery-1.7.1.min.js) در حال حاضر minify شده اند . 

اجرای فایل های bundle و minify شده Javascript  :

اجرای فایل های JavaScript در Development Environmentها ، به این دلیل که فایل ها نه Bubdle شده اند و نه modify ،  کار ساده ای است . شما امکان debug کردن یک فایل جاوااسکریپ Bundle و minify شده را نیز دارید . با استفاده از ابزار توسعه دهندگان در IE F12 ، شما توسط دو عملکرد زیر میتوانید یک فایل جاوااسکریپت Bundle و Modify شده را اجرا کنید :

1. Script tab را انتخاب کنید ، سپس دکمه Start debugging را بزنید .
2. Bundle ای را که فایل جاوااسکریپت را شامل میشود را انتخاب کنید .

3.  بوسیله انتخاب دکمه Configuration  ، فایل های minified javascript را Format کنید 

سپس Format JavaScript را انتخاب کنید . 

4. در Search Script ، نام تابعی را که میخواهید آن را debuge کنید را وارد کنید . همانطور که در تصویر مشاهده میکنید ما قصد اجرای AddAltToImg  را داریم :



برای کسب اطلاعات بیشتر در مورد F12 Developer Tools میتوانید اینجا کلیک کنید :

کنترل Bundling  و Minification :

Bundling  و Minification با تنظیم مقدار در صفت debuge از مولفه Compilation در web.config میتواند فعال یا غیر فعال شود . در XML زیر ، مقدار debug را true قرار دادیم ، که این بدین معناست که Bundling  و Minification غیر فعال خواهد بود . 

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

برای فعال سازی Bundling  و Minification ، مقدار debug را مساوی با false قرار دهید . شما میتوانید با استفاده از EnableOptimizations در کلاس BundleTable  می‌توانید web.config را override کنید . تکه کد زیر Bundling  و Minification را فعال میکند و هرگونه تنظیماتی در web.config را override میکند . 

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

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

استفاده از Bundling  و Minification در ASP.Net MVC :

در این قسمت ما یک پروژه ASP.Net ایجاد خواهیم کرد و بررسی Bundling  و Minification خواهیم پرداخت . ابتدا یک پروژه ASP.Net MVC با نام MvcBM ایجاد کنید ، بدون تغییر تنظیمات ، پروژه را ایجاد کنید . 

فایل App_Start\BundleConfig.cs را باز کرده و متد RegistreBundle که برای ایجاد ، ثبت و پیکربندی bnundle ها مورد استفاده قرار می‌گیرد را بررسی کنید . کد زیر یک قسمت از متد RegistreBundle  را نمایش میدهد . 

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

کد بالا یک Javascript bundle جدید با نام bundles/jquery/~ ایجاد میکند ،  که  تمام فایل هایی که در فولدر Script با رشته "Scripts/jquery-{version}.js/~" همخوانی دارند را ، شامل می‌شود . برای ASP.Net MVC 4 ، این بدان معناست که فایل jquery-1.7.1.js به bundle اضافه خواهد شد . 

- انتخاب فایل min. برای انتشار زمانی که FileX.min.js و FileX.js وجود دارند .
- انتخاب نسخه غیر min. برای debug
- نادیده گرفتن فایل vsdoc- همانند jquery-1.7.1-vsdoc.js که فقط توسط intelliScence مورد استفاده قرار میگیرد .


{Version} برای ایجاد خودکار jQuery bundle با استفاده از فایل های javascript مناسب موجود در فایل Scripts میباشد . در این مثال ، استفاده از این wild card مزیت های زیر را به دنبال دارد :

• در زمان بروزرسانی فایل های Script با استفاده از NuGetدیگر نیازی به ویرایش کدهای قبلی bunle نیست .
• بصورت خودکار برای پیکربندی debuge از نسخه کامل  فایل ها استفاده میکند و برای انتشار آنها از نسخه min. استفاده خواهد کرد . 

استفاده از CDN :

تکه کد زیر local jQuery bundle را با CDN jQuery bundle جابجا میکند :

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 = "http://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.
}


در کد بالا ،در زمانی که در حالت انتشار قرار دارد از CDN استفاده خواهد کرد و زمانی که در حالت debug قرار دارد jQuery ه بصورت محلی واکشی خواهند شد .  زمانی که از CDN استفاده میکنیم ، برای زمانی که CDN با خطا مواجه شد باید یک مکانیزم برگشتن داشته باشید . تکه کد علامت دار شده زیر ، برای زمانی است که CDN با خطا مواجه و fail شد . 

        </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>

ایجاد یک Bundle :

متد Include از کلاس Bundle یک آرایه از string را دریافت میکند ، که هر رشته یک آدرس مجازی به منبع است . کد زیر از متد RegisterBundles در فایل App_Start\BundleConfig.cs ، چگونگی اضافه شدن چندین فایل به bundle را نمایش میدهد . 

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"));

متد IncludeDirectory کلاس Bundle برای اضافه کردن تمام فایل ها به یک دایرکتوری فراهم شده است که با یک الگوی جستجو همخوانی دارد . IncludeDirectory API کلاس Bundle در زیر نمایش داده شده است :

 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.

Bundel ها با استفاده از متد Render در View ها مورد استفاده قرار میگیرند . Styles.Render برای CSS و Scripts.Render برای Javascript / قسمت های علامت گذاری شده در فایل Views\Shared\_Layout.cshtml چگونگی آدرس دهی پیش فرض ASP.Net Project به CSS bundle  و  Javascript bundle ها را نمایش میدهد . 

<!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>

توجه داشته باشید که متد Render یک آرایه ای از رشته ها را دریافت میکند . پس شما این امکان را دارید که ، در یک خط چندین Bundle را اضافه کنید . شما نیز برای تولید URL میتوانید از متد URL استفاده کنید . فرض بر این است که شما میخواهید از صفت جدید async استفاده کنید . تکه کد زیر چگونگی استفاده از متد URL را به شما نشان میدهد : 

<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>

استفاده از "*" برای انتخاب فایل ها :

آدرس مجازی مشخص شده در متد Include و search pattern در متد IncludeDirectory میتواند کاراکتر  "*" را به عنوان پسوند یا پیشوند در آخرین آدرس دریافت کند . رشته جستجو حساس به بزرگ و کوچک بودن حروف نمی‎‌باشد . متد IncludeDirectory گزینه ای برای جستجو subdirectories دارد . 

پروژه ای را با فایل های javaScript زیر در نظر بگیرید :

• Scripts\Common\AddAltToImg.js

• Scripts\Common\ToggleDiv.js

• Scripts\Common\ToggleImg.js

• Scripts\Common\Sub1\ToggleLinks.js




جدول زیر فایل هایی که توسط WildCard به bundle اضافه شده اند را نمایش میدهند. 




اضافه کردن فایل ها به bundle بصورت صریح به دلایلی که در زیر ارائه میشود نسبت به بارگذاری Wildcard ها اولویت داده میشود :

• اضافه کردن فایل ها توسط wildcard بارگذاری را به ترتیب حروف الفبا انجام میدهد ، که آن چیزی نیست که ما می‌خواهیم . ترتیب فایل های CSS , JavaScript برای ما اهمیت دارد و ما نمیخواهیم که آنها بر اساس حروف الفبا مرتب سازی شده باشند . شما با پیاده سازی IBundleOrderer میتوانید طریقه ی مرتب سازی را تعریف کنید ، اما استفاده از اضافه کردن صریح شما را با مشکلات و خطاهای کمتری مواجه خواهد کرد . 

• اضافه شدن View یک فایل مشخص به دایرکتوری با استفاده از بارگذاری Wildcard میتواند شامل همه
view referencing های bundle شده باشد . اگر view یک script مشخص به bundle اضافه شود ، شما در فایل های دیگری که به این bundle ارجاع داده شده اند ، خطای Javascript دریافت خواهید کرد . 

Bundle Caching :

Bundle زمان انقضای HTTP را یک سال پس از زمانی که bundle ایجاد می‌شود ، تنظیم میکند .  اگر شما آن را به صفحه قبلی هدایت کنید ، وضعیتی بوجود می ‌آید که درخواستی برای Bundel وجود ندارد ، بدین صورت که نه هیچ درخواست HTTP GET از سمت IE برای bundle وجود دارد ، و نه هیچ HTTP 304 response از سمت سرور . شما میتوانید این شرایط را تغییر دهید ، بدین صورت که با هر بار زدن F5 درخواستی برای bundle ارسال شود . 

تصویر زیر caching tab را نمایش میدهد :



درخواست 
http://localhost/MvcBM_time/bundles/AllMyScripts?v=r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81 برای "bundle "AllMyScripts میباشد و شامل
query string زیر میباشد :
v=r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81 . رشته کوئری v یک رشته منحصر به فرد است که برای caching مورد استفاده قرار میگیرد . تا زمانی که bundle تغییری نکند ، برنامه ، برای درخواست
"bundle "AllMyScripts از این  token استفاده خواهد کرد . اگر هر گونه تغییری در bundle ایجاد شود    
ASP.NET optimization framework یک token جدید تولید خواهد کرد ، و تضمین خواهد کرد که با درخواست ، مرورگر آخرین نسخه از bundle را دریافت خواهد کرد . 


LESS, CoffeeScript, SCSS, Sass Bundling :

فریمورک Bundling  و Minification یک مکانیزمی را برای پردازش زبان های میانی نظیر
SCSS SassLESS یا  Coffeescript ، فراهم می‌آورند  . برای مثال ، برای اضافه کردن یک فایل LESS به پروژه خود :

1. یک فولدر برای محتوای LESS خود ایجاد کنید . در این مثال ما از فولدر Content\MyLess استفاده خواهیم کرد . 
2. از طریق Nuget پکیج dotless  را به پروژه خود اضافه کنید . 




3. یک کلاس برای پیاده سازی اینترفیس IBundleTransform ایجاد کنید . برای انتقال less. کدهای زیر را به پروژه خود اضافه کنید .

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. یک فایل  bundle of less با LessTransform و انتقال  CssMinify ، ایجاد کنید . کدهای زیر را به متد Registerbundle در فایل 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. کد زیر را به هر view ای که باید به فایل Less ارجاع داده شود اضافه کنید :

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


بررسی Bundle :
یک قرار داد خوب برای نام گذاری ،  زمانی که یک Bundle جدید ایجاد میکنید برای نام گذاری آن از "bundle " به عنوان پیشوند استفاده کنید ، این کار از تداخل و تصادم اسم ها جلوگیری میکند . 

زمانی که یک فایل را در bundle بروزرسانی می‌کنید ، یک Token جدید ایجاد میشود و با درخواست بعدی از سمت کاربر ، دوباره باید فایل bundle دانلود شود . در گذشته که از bundle استفاده نمیشد و فایل ها بصورت انفرادی قرار میگرفتند ، در زمان ایجاد تغییرات ، فقط فایل هایی که دچار تغییرات شده بودند دوباره دانلود میشدند . 

Bundling  و Minification زمان بارگذاری صفحه نخست را بهبود میبخشد . زمانی که یک صفحه درخواست داده میشود ، مرورگر دارایی های (CSS و JavaScript و imageها) سایت را cach میکند ، پس زمانی که دوباره سایت درخواست داده شود Bundling  و Minification  کار خاصی را انجام نمیدهند . اگر شما زمان انقضای درستی بر روی header خود تنظیم نکنید ، و از Bundling  و Minification  نیز استفاده نکنید ، مرورگر دارایی ها را علامت گذاری میکند و بعد از چند روز مرورگر برای هر دارایی نیازمند اعتبار سنجی میشود .

محدودیت 6 تایی simultaneous connections به ازای هر hostname میتواند با استفاده از CDN تغییر کند . به این دلیل که CDn یک hstname متفاوت با hostname سایت شما دارد ، در زمان استفاده از CDN محدودیت 6 تایی اعمال نمیشود . 

بهتر است که bundleها با صفحات مورد نیاز تقسیم بندی شوند . برای مثال ، در tempalte پیش فرض ASP.Net MVC برای یک برنامه اینترنت ، jQuery Validation Bundle از jQuery جدا است ، و این بدین دلیل است که viewهای پیش فرض ایجاد شده دارای ورودی نیستند و مقداری را نیز پست نمیکنند،برای همین آنها شامل validation bundle نمیباشند .

برنامه نویسان

نویسنده 3355 مقاله در برنامه نویسان

کاربرانی که از نویسنده این مقاله تشکر کرده اند

در صورتی که در رابطه با این مقاله سوالی دارید، در تاپیک های انجمن مطرح کنید