自动检测 ASP.NET MVC 捆绑中的缩小失败

Automatically detect minification failures in ASP.NET MVC bundling

我不时注意到 CSS 由 ASP.NET MVC 生成的包顶部的以下错误消息:

/* Minification failed. Returning unminified contents.
(7933,26): run-time error CSS1019: Unexpected token, found ':'
(7933,26): run-time error CSS1042: Expected function, found ':'
(7933,26): run-time error CSS1062: Expected semicolon or closing curly-brace, found ':'
(7934,30): run-time error CSS1019: Unexpected token, found ':'
(7934,30): run-time error CSS1042: Expected function, found ':'
(7934,30): run-time error CSS1062: Expected semicolon or closing curly-brace, found ':'
 */

这些错误总是悄无声息地通过构建、部署和单元测试,很难被注意到。有什么解决方案可以自动捕获它们吗?很难从单元测试中得到它,因为没有内容文件夹被复制到单元测试项目中。最好这应该使构建或至少单元测试失败。

我有同样的问题,想对缩小进行单元测试,在一些错误开始尝试模拟 BundleContext 之后,我选择直接测试 webgrease Microsoft.Ajax.Utilities.Minifier 提供的缩小,轻微问题是 BundleItem 是 System.Web.Optimization 内部的,所以我不得不反思:

  //arrange
  BundleCollection bundles = new BundleCollection();

  // my static bundler config in mvc
  BundleConfig.RegisterBundles(bundles); 

  // act and assert
  Assert.Multiple(() =>
  {
    foreach (var bundle in bundles)
    {
      // CAUTION: Reflection to private member!
      var items = typeof(Bundle).GetProperty("Items", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(bundle) as IEnumerable<Object>;
      foreach (Object item in items)
      {
        var minifier = new Minifier();
        var type = item.GetType();
        var path = type.GetProperty("VirtualPath")?.GetValue(item) as String;

        // todo: handle "BundleDirectoryItem" too ...
        if (type.Name == "BundleItem")
        {
          // todo: transform your virtual path to a physical path to the file at design time
          var file = File.ReadAllText(path);
          String min;
          if (path.EndsWith("css"))
          {
            min = minifier.MinifyStyleSheet(file);
          }
          else
          {
            min = minifier.MinifyJavaScript(file);
          }
          Assert.IsNotNull(min, $"Failed to get minified output for '{path}'");
          Assert.Zero(minifier.Errors.Count, $"Minification failed for '{path}', errors:\r\n{String.Join("\r\n", minifier.Errors)}");
        }
      }
    }
  });

在一个不包含测试的项目中,使用 ,我能够在加载系统时显示缩小失败的警报,虽然它没有导致编译失败,但我们设法捕获开发环境中的故障​​:

public static IHtmlString Render(this BundleCollection bundles, string bundleVirtualPath) {
    Bundle bundle = bundles.GetBundleFor(bundleVirtualPath);
    List<IHtmlString> renderTags = new List<IHtmlString>();

    ... omitted for simplification

    List<string> errors;
    if (!bundle.IsValid(out errors)) {
        renderTags.Add(new HtmlString($"<script type='text/javascript'>alert('{HttpUtility.JavaScriptStringEncode(string.Join(Environment.NewLine, errors))}');</script>"));
    }

    return MvcHtmlString.Create(string.Join(Environment.NewLine, renderTags));
}


private static bool IsValid(this Bundle bundle, out List<string> errors) {
    errors = new List<string>();

    // Get items in included order
    BundleContext bundleContext = new BundleContext(new HttpContextWrapper(HttpContext.Current), new BundleCollection(), Guid.NewGuid().ToString());
    IEnumerable<BundleFile> bundleFiles = bundle.EnumerateFiles(bundleContext);
    bool isStyleBundle = (bundle is StyleBundle);

    foreach (BundleFile bundleFile in bundleFiles) {
        string physicalPath = HttpContext.Current.Server.MapPath(bundleFile.IncludedVirtualPath),
                fileContent = File.ReadAllText(physicalPath);

        // Create minifier used by webgrease System.Web.Optimization
        var minifier = new Microsoft.Ajax.Utilities.Minifier();
        if (isStyleBundle) {
            minifier.MinifyStyleSheet(fileContent);
        } else {
            minifier.MinifyJavaScript(fileContent);
        }

        // Verify error in minification
        if (minifier.Errors.Any()) {
            errors.Add($"Minification failed for {bundleFile.IncludedVirtualPath}, errors:{Environment.NewLine}{string.Join(Environment.NewLine, minifier.Errors)}");
        }
    }

    return errors.Any();
}

希望以上代码对以后的人有所帮助。