拡張Bundleは動的Bundleとjavascriptの混同をサポートする


拡張Bundleは動的Bundleとjavascriptの混同をサポートする
2つの目的:
  • GlobalにBundleを毎回追加することなく、動的ページ上のBundleをサポートします.
  • Javascript混同をサポート
  • ASP.NET MVC持参Bundle
    通常ASP.NET MVC 4.0以降持参したBundleは以下の通り.
    BundleConfig.RegisterBundles(BundleTable.Bundles);
    public class BundleConfig
        {
            // For more information on Bundling, visit http://go.microsoft.com/fwlink/?LinkId=254725
            public static void RegisterBundles(BundleCollection bundles)
            {
                // Use the development version of Modernizr to develop with and learn from. Then, when you're
                // ready for production, use the build tool at http://modernizr.com to pick only the tests you need.
                bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(
                            "~/Scripts/modernizr-*"));
    
                bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                            "~/Scripts/jquery-{version}.js"));
    
    #if !DEBUG
                BundleTable.EnableOptimizations = true;
    #endif
            }
        }

    ページでの使用:
    @Scripts.Render("~/bundles/jquery")

    新しいダイナミックBundle
    重要でないページもあれば、わざわざBundleを作成する必要はないと思いますが、ページにBundleを動的に作成すると便利です.たとえば、ページで次のように使用します.
    @Html.Style("~/Scripts/JQueryUI2/themes/smoothness/jquery.ui.theme.css", "~/Scripts/JQueryUI2/themes/smoothness/jquery.ui.menu.css")
            @Html.Script("~/Scripts/JQueryUI2/ui/jquery.ui.core.js", "~/Scripts/JQueryUI2/ui/jquery.ui.position.js", "~/Scripts/JQueryUI2/ui/jquery.ui.widget.js", "~/Scripts/JQueryUI2/ui/jquery.ui.menu.js")

    もちろん上はStyleは自分で作成した拡張子で、次の拡張子のソースコードが貼られています.
    public static class Extension
        {
            public static IHtmlString Script(this HtmlHelper helper, params string[] urls)
            {
                var bundleDirectory = "~/bundles/" + MakeBundleName("js", urls);
                var bundle = BundleTable.Bundles.GetBundleFor(bundleDirectory);
                if (bundle == null)
                {
                    var transform = new JavascriptObfuscator();
                    bundle = new ScriptBundle(bundleDirectory).Include(urls);
                    bundle.Transforms.Add(transform);
                    BundleTable.Bundles.Add(bundle);
                }
                return Scripts.Render(bundleDirectory);
            }
    
            public static IHtmlString Style(this HtmlHelper helper, params string[] urls)
            {
                var bundleDirectory = "~/bundles/" + MakeBundleName("css", urls);
                var bundle=BundleTable.Bundles.GetBundleFor(bundleDirectory);
                if (bundle == null)
                {
                    bundle = new StyleBundle(bundleDirectory).Include(urls);
                    BundleTable.Bundles.Add(bundle);
                }
                return Styles.Render(bundleDirectory);
            }
    
            private static string MakeBundleName(string type, params string[] urls)
            {
                var array =
                    urls.SelectMany(url => url.Split('/'))
                        .SelectMany(url => url.Split('.'))
                        .Distinct()
                        .Except(new[] {"~", type});
    
                return string.Join("-", array);
            }
        }

    上のコードは分かりやすいと信じています.説明する必要はありません.上はただの考えで、人々は勝手にあなたの創作を発揮することができます.ただ、Javascript混同器を追加し、次のようなコードを生成するために、
    eval(function(p,a,c,k,e,d){e=function(c){return(c35?String.fromChar

    混同器コードはネットで探したもので、以下の通りです.
    /// 
        /// Packs a javascript file into a smaller area, removing unnecessary characters from the output.
        /// 
        public class ECMAScriptPacker : IHttpHandler
        {
            /// 
            /// The encoding level to use. See http://dean.edwards.name/packer/usage/ for more info.
            /// 
            public enum PackerEncoding { None = 0, Numeric = 10, Mid = 36, Normal = 62, HighAscii = 95 };
    
            private PackerEncoding encoding = PackerEncoding.Normal;
            private bool fastDecode = true;
            private bool specialChars = false;
            private bool enabled = true;
    
            string IGNORE = "$1";
    
            /// 
            /// The encoding level for this instance
            /// 
            public PackerEncoding Encoding
            {
                get { return encoding; }
                set { encoding = value; }
            }
    
            /// 
            /// Adds a subroutine to the output to speed up decoding
            /// 
            public bool FastDecode
            {
                get { return fastDecode; }
                set { fastDecode = value; }
            }
    
            /// 
            /// Replaces special characters
            /// 
            public bool SpecialChars
            {
                get { return specialChars; }
                set { specialChars = value; }
            }
    
            /// 
            /// Packer enabled
            /// 
            public bool Enabled
            {
                get { return enabled; }
                set { enabled = value; }
            }
    
            public ECMAScriptPacker()
            {
                Encoding = PackerEncoding.Normal;
                FastDecode = true;
                SpecialChars = false;
            }
    
            /// 
            /// Constructor
            /// 
            /// The encoding level for this instance
            /// Adds a subroutine to the output to speed up decoding
            /// Replaces special characters
            public ECMAScriptPacker(PackerEncoding encoding, bool fastDecode, bool specialChars)
            {
                Encoding = encoding;
                FastDecode = fastDecode;
                SpecialChars = specialChars;
            }
    
            /// 
            /// Packs the script
            /// 
            /// the script to pack
            /// the packed script
            public string Pack(string script)
            {
                if (enabled)
                {
                    script += "
    "; script = basicCompression(script); if (SpecialChars) script = encodeSpecialChars(script); if (Encoding != PackerEncoding.None) script = encodeKeywords(script); } return script; } //zero encoding - just removal of whitespace and comments private string basicCompression(string script) { ParseMaster parser = new ParseMaster(); // make safe parser.EscapeChar = '\\'; // protect strings parser.Add("'[^'\
    \\r]*'", IGNORE); parser.Add("\"[^\"\
    \\r]*\"", IGNORE); // remove comments parser.Add("\\/\\/[^\
    \\r]*[\
    \\r]"); parser.Add("\\/\\*[^*]*\\*+([^\\/][^*]*\\*+)*\\/"); // protect regular expressions parser.Add("\\s+(\\/[^\\/\
    \\r\\*][^\\/\
    \\r]*\\/g?i?)", "$2"); parser.Add("[^\\w\\$\\/'\"*)\\?:]\\/[^\\/\
    \\r\\*][^\\/\
    \\r]*\\/g?i?", IGNORE); // remove: ;;; doSomething(); if (specialChars) parser.Add(";;[^\
    \\r]+[\
    \\r]"); // remove redundant semi-colons parser.Add(";+\\s*([};])", "$2"); // remove white-space parser.Add("(\\b|\\$)\\s+(\\b|\\$)", "$2 $3"); parser.Add("([+\\-])\\s+([+\\-])", "$2 $3"); parser.Add("\\s+"); // done return parser.Exec(script); } WordList encodingLookup; private string encodeSpecialChars(string script) { ParseMaster parser = new ParseMaster(); // replace: $name -> n, $$name -> na parser.Add("((\\$+)([a-zA-Z\\$_]+))(\\d*)", new ParseMaster.MatchGroupEvaluator(encodeLocalVars)); // replace: _name -> _0, double-underscore (__name) is ignored Regex regex = new Regex("\\b_[A-Za-z\\d]\\w*"); // build the word list encodingLookup = analyze(script, regex, new EncodeMethod(encodePrivate)); parser.Add("\\b_[A-Za-z\\d]\\w*", new ParseMaster.MatchGroupEvaluator(encodeWithLookup)); script = parser.Exec(script); return script; } private string encodeKeywords(string script) { // escape high-ascii values already in the script (i.e. in strings) if (Encoding == PackerEncoding.HighAscii) script = escape95(script); // create the parser ParseMaster parser = new ParseMaster(); EncodeMethod encode = getEncoder(Encoding); // for high-ascii, don't encode single character low-ascii Regex regex = new Regex( (Encoding == PackerEncoding.HighAscii) ? "\\w\\w+" : "\\w+" ); // build the word list encodingLookup = analyze(script, regex, encode); // encode parser.Add((Encoding == PackerEncoding.HighAscii) ? "\\w\\w+" : "\\w+", new ParseMaster.MatchGroupEvaluator(encodeWithLookup)); // if encoded, wrap the script in a decoding function return (script == string.Empty) ? "" : bootStrap(parser.Exec(script), encodingLookup); } private string bootStrap(string packed, WordList keywords) { // packed: the packed script packed = "'" + escape(packed) + "'"; // ascii: base for encoding int ascii = Math.Min(keywords.Sorted.Count, (int)Encoding); if (ascii == 0) ascii = 1; // count: number of words contained in the script int count = keywords.Sorted.Count; // keywords: list of words contained in the script foreach (object key in keywords.Protected.Keys) { keywords.Sorted[(int)key] = ""; } // convert from a string to an array StringBuilder sbKeywords = new StringBuilder("'"); foreach (string word in keywords.Sorted) sbKeywords.Append(word + "|"); sbKeywords.Remove(sbKeywords.Length - 1, 1); string keywordsout = sbKeywords.ToString() + "'.split('|')"; string encode; string inline = "c"; switch (Encoding) { case PackerEncoding.Mid: encode = "function(c){return c.toString(36)}"; inline += ".toString(a)"; break; case PackerEncoding.Normal: encode = "function(c){return(c35?String.fromCharCode(c+29):c.toString(36))}"; inline += ".toString(a)"; break; case PackerEncoding.HighAscii: encode = "function(c){return(c (int)PackerEncoding.Normal || fastDecode) { // insert the encode function r = new Regex("\\{"); unpack = r.Replace(unpack, "{e=" + encode + ";", 1); } else { r = new Regex("e\\(c\\)"); unpack = r.Replace(unpack, inline); } // no need to pack the boot function since i've already done it string _params = "" + packed + "," + ascii + "," + count + "," + keywordsout; if (fastDecode) { //insert placeholders for the decoder _params += ",0,{}"; } // the whole thing return "eval(" + unpack + "(" + _params + "))
    "; } private string escape(string input) { Regex r = new Regex("([\\\\'])"); return r.Replace(input, "\\$1"); } private EncodeMethod getEncoder(PackerEncoding encoding) { switch (encoding) { case PackerEncoding.Mid: return new EncodeMethod(encode36); case PackerEncoding.Normal: return new EncodeMethod(encode62); case PackerEncoding.HighAscii: return new EncodeMethod(encode95); default: return new EncodeMethod(encode10); } } private string encode10(int code) { return code.ToString(); } //lookups seemed like the easiest way to do this since // I don't know of an equivalent to .toString(36) private static string lookup36 = "0123456789abcdefghijklmnopqrstuvwxyz"; private string encode36(int code) { string encoded = ""; int i = 0; do { int digit = (code / (int)Math.Pow(36, i)) % 36; encoded = lookup36[digit] + encoded; code -= digit * (int)Math.Pow(36, i++); } while (code > 0); return encoded; } private static string lookup62 = lookup36 + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; private string encode62(int code) { string encoded = ""; int i = 0; do { int digit = (code / (int)Math.Pow(62, i)) % 62; encoded = lookup62[digit] + encoded; code -= digit * (int)Math.Pow(62, i++); } while (code > 0); return encoded; } private static string lookup95 = "¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ"; private string encode95(int code) { string encoded = ""; int i = 0; do { int digit = (code / (int)Math.Pow(95, i)) % 95; encoded = lookup95[digit] + encoded; code -= digit * (int)Math.Pow(95, i++); } while (code > 0); return encoded; } private string escape95(string input) { Regex r = new Regex("[\xa1-\xff]"); return r.Replace(input, new MatchEvaluator(escape95Eval)); } private string escape95Eval(Match match) { return "\\x" + ((int)match.Value[0]).ToString("x"); //return hexadecimal value } private string encodeLocalVars(Match match, int offset) { int length = match.Groups[offset + 2].Length; int start = length - Math.Max(length - match.Groups[offset + 3].Length, 0); return match.Groups[offset + 1].Value.Substring(start, length) + match.Groups[offset + 4].Value; } private string encodeWithLookup(Match match, int offset) { return (string)encodingLookup.Encoded[match.Groups[offset].Value]; } private delegate string EncodeMethod(int code); private string encodePrivate(int code) { return "_" + code; } private WordList analyze(string input, Regex regex, EncodeMethod encodeMethod) { // analyse // retreive all words in the script MatchCollection all = regex.Matches(input); WordList rtrn; rtrn.Sorted = new StringCollection(); // list of words sorted by frequency rtrn.Protected = new HybridDictionary(); // dictionary of word->encoding rtrn.Encoded = new HybridDictionary(); // instances of "protected" words if (all.Count > 0) { StringCollection unsorted = new StringCollection(); // same list, not sorted HybridDictionary Protected = new HybridDictionary(); // "protected" words (dictionary of word->"word") HybridDictionary values = new HybridDictionary(); // dictionary of charCode->encoding (eg. 256->ff) HybridDictionary count = new HybridDictionary(); // word->count int i = all.Count, j = 0; string word; // count the occurrences - used for sorting later do { word = "$" + all[--i].Value; if (count[word] == null) { count[word] = 0; unsorted.Add(word); // make a dictionary of all of the protected words in this script // these are words that might be mistaken for encoding Protected["$" + (values[j] = encodeMethod(j))] = j++; } // increment the word counter count[word] = (int)count[word] + 1; } while (i > 0); /* prepare to sort the word list, first we must protect words that are also used as codes. we assign them a code equivalent to the word itself. e.g. if "do" falls within our encoding range then we store keywords["do"] = "do"; this avoids problems when decoding */ i = unsorted.Count; string[] sortedarr = new string[unsorted.Count]; do { word = unsorted[--i]; if (Protected[word] != null) { sortedarr[(int)Protected[word]] = word.Substring(1); rtrn.Protected[(int)Protected[word]] = true; count[word] = 0; } } while (i > 0); string[] unsortedarr = new string[unsorted.Count]; unsorted.CopyTo(unsortedarr, 0); // sort the words by frequency Array.Sort(unsortedarr, (IComparer)new CountComparer(count)); j = 0; /*because there are "protected" words in the list we must add the sorted words around them */ do { if (sortedarr[i] == null) sortedarr[i] = unsortedarr[j++].Substring(1); rtrn.Encoded[sortedarr[i]] = values[i]; } while (++i < unsortedarr.Length); rtrn.Sorted.AddRange(sortedarr); } return rtrn; } private struct WordList { public StringCollection Sorted; public HybridDictionary Encoded; public HybridDictionary Protected; } private class CountComparer : IComparer { HybridDictionary count; public CountComparer(HybridDictionary count) { this.count = count; } #region IComparer Members public int Compare(object x, object y) { return (int)count[y] - (int)count[x]; } #endregion } #region IHttpHandler Members public void ProcessRequest(HttpContext context) { // try and read settings from config file if (System.Configuration.ConfigurationSettings.GetConfig("ecmascriptpacker") != null) { NameValueCollection cfg = (NameValueCollection) System.Configuration.ConfigurationSettings.GetConfig("ecmascriptpacker"); if (cfg["Encoding"] != null) { switch (cfg["Encoding"].ToLower()) { case "none": Encoding = PackerEncoding.None; break; case "numeric": Encoding = PackerEncoding.Numeric; break; case "mid": Encoding = PackerEncoding.Mid; break; case "normal": Encoding = PackerEncoding.Normal; break; case "highascii": case "high": Encoding = PackerEncoding.HighAscii; break; } } if (cfg["FastDecode"] != null) { if (cfg["FastDecode"].ToLower() == "true") FastDecode = true; else FastDecode = false; } if (cfg["SpecialChars"] != null) { if (cfg["SpecialChars"].ToLower() == "true") SpecialChars = true; else SpecialChars = false; } if (cfg["Enabled"] != null) { if (cfg["Enabled"].ToLower() == "true") Enabled = true; else Enabled = false; } } // try and read settings from URL if (context.Request.QueryString["Encoding"] != null) { switch (context.Request.QueryString["Encoding"].ToLower()) { case "none": Encoding = PackerEncoding.None; break; case "numeric": Encoding = PackerEncoding.Numeric; break; case "mid": Encoding = PackerEncoding.Mid; break; case "normal": Encoding = PackerEncoding.Normal; break; case "highascii": case "high": Encoding = PackerEncoding.HighAscii; break; } } if (context.Request.QueryString["FastDecode"] != null) { if (context.Request.QueryString["FastDecode"].ToLower() == "true") FastDecode = true; else FastDecode = false; } if (context.Request.QueryString["SpecialChars"] != null) { if (context.Request.QueryString["SpecialChars"].ToLower() == "true") SpecialChars = true; else SpecialChars = false; } if (context.Request.QueryString["Enabled"] != null) { if (context.Request.QueryString["Enabled"].ToLower() == "true") Enabled = true; else Enabled = false; } //handle the request TextReader r = new StreamReader(context.Request.PhysicalPath); string jscontent = r.ReadToEnd(); r.Close(); context.Response.ContentType = "text/javascript"; context.Response.Output.Write(Pack(jscontent)); } public bool IsReusable { get { if (System.Configuration.ConfigurationSettings.GetConfig("ecmascriptpacker") != null) { NameValueCollection cfg = (NameValueCollection) System.Configuration.ConfigurationSettings.GetConfig("ecmascriptpacker"); if (cfg["IsReusable"] != null) if (cfg["IsReusable"].ToLower() == "true") return true; } return false; } } #endregion }

    上のコードには作者の情報がいくつかあります.
    これを使用するには、BundleTransformを作成します.
    public class JavascriptObfuscator : IBundleTransform
        {
            public void Process(BundleContext context, BundleResponse response)
            {
                var p = new ECMAScriptPacker(ECMAScriptPacker.PackerEncoding.Normal, true, false);
    
                response.Content = p.Pack(response.Content);
            }
        }

    もちろんこの混同器はHandlerとして使用することができ、あまり説明しません.
    まとめ
    上のコードはすべて接続されていて、私が使いたい2つの機能です.これは自分の少しの私欲のために作成された小さなツールにすぎず、一部の人に役に立つことを望んでいます.ちなみに、ネット上にはBundle Transformer:YUI 1.8.0などの強力なBundleプラグインがあります.興味があれば行ってみてください.