using System;
using System.IO;
using System.Text;
using System.Web;
using System.Web.Caching;
using Yahoo.Yui.Compressor;
public class CombineFiles : IHttpHandler
{
private const string CacheKeyFormat = "_CombineCache_{0}_";
private const bool EnableCompress = true;
public bool IsReusable => false;
public void ProcessRequest(HttpContext context)
{
var request = context.Request;
var response = context.Response;
string type = request.QueryString["type"]; // "js" 或 "css"
if (string.IsNullOrEmpty(type) || (type != "js" && type != "css"))
{
response.StatusCode = 400;
response.End();
return;
}
// 設定正確 MIME
response.ContentType = type == "js" ? "application/javascript" : "text/css";
string cacheKey = string.Format(CacheKeyFormat, type);
var cacheItem = HttpRuntime.Cache[cacheKey] as CacheItem;
if (cacheItem == null)
{
// 首次建立:掃描目錄、合併、壓縮
string dirPath = context.Server.MapPath(type == "js" ? "~/Scripts" : "~/Content");
if (!Directory.Exists(dirPath))
{
response.StatusCode = 404;
response.End();
return;
}
var sb = new StringBuilder();
foreach (string file in Directory.GetFiles(dirPath, "*." + type, SearchOption.TopDirectoryOnly))
{
string content = File.ReadAllText(file, Encoding.UTF8);
sb.AppendLine(content);
}
string combined = sb.ToString();
// 壓縮
if (EnableCompress)
{
if (type == "js")
combined = JavaScriptCompressor.Compress(combined);
else
combined = CssCompressor.Compress(combined);
}
var expires = DateTime.Now.AddDays(30);
cacheItem = new CacheItem { Content = combined, Expires = expires, Type = type };
HttpRuntime.Cache.Insert(cacheKey, cacheItem, null, expires, Cache.NoSlidingExpiration);
}
// 條件式請求支援
string ifModifiedSince = request.Headers["If-Modified-Since"];
if (!string.IsNullOrEmpty(ifModifiedSince) &&
DateTime.TryParse(ifModifiedSince, out DateTime clientDate) &&
cacheItem.Expires <= clientDate)
{
response.StatusCode = 304;
response.StatusDescription = "Not Modified";
response.End();
return;
}
// 輸出內容
response.Write(cacheItem.Content);
SetClientCaching(response, cacheItem.Expires);
}
private void SetClientCaching(HttpResponse response, DateTime expires)
{
response.Cache.SetETag(DateTime.Now.Ticks.ToString());
response.Cache.SetLastModified(DateTime.Now);
response.Cache.SetCacheability(HttpCacheability.Public);
response.Cache.SetMaxAge(TimeSpan.FromDays(30));
response.Cache.SetSlidingExpiration(true);
response.Cache.SetExpires(expires);
}
private class CacheItem
{
public string Type { get; set; }
public string Content { get; set; }
public DateTime Expires { get; set; }
}
}
<system.webServer>
<handlers>
<add name="CombineFiles" verb="GET" path="CombineFiles.axd" type="YourNamespace.CombineFiles, YourAssembly" preCondition="integratedMode" />
</handlers>
</system.webServer>
<!-- 頁面頭部 -->
<link rel="stylesheet" href="/CombineFiles.axd?type=css" />
<!-- 頁面底部 -->
<script src="/CombineFiles.axd?type=js"></script>
| 優化點 | 說明 | 實作提示 |
|---|---|---|
| 檔案排序/依賴管理 | jQuery 必須在插件前載入 | 維護 `manifest.json` 列出載入順序,Handler 依序讀取 |
| 版本指紋 | 部署新版需刷新客戶端快取 | URL 加入 `?v=20240115` 或路徑包含 hash `/CombineFiles.axd?v=abc123` |
| GZIP/Br 壓縮 | 傳輸再壓縮 70%+ | IIS 動態壓縮模組自動處理,或 Handler 手動 `GZipStream` |
| CDN 整合 | 靜態資源卸載至 CDN | Handler 僅處理專案私有資源,第三方庫走 CDN |
| Source Map | 除錯時對應原始碼 | YUI Compressor 可輸出 `.map`,開發環境關閉壓縮 |
| 非同步載入 | 不阻塞頁面渲染 | `
欄目導航
Copyright © 程式碼之家 All Rights Reserved
|