Minifying Inline Css, Js and Html using Tag Helpers in ASP.NET Core

 

        Introduction:

 

                I think Tag Helpers is one of coolest feature in ASP.NET Core. Tag helpers allows our server side code to participate in generating final html response using normal (or custom) html tags. In this article, I will show you how we can leverage tag helpers to minify inline css and inline javascript. I will also show you how we can minify the html.           

 

        Description:

 

                   Note that I am using RC1 at the time of writing. First we need the css, javascript and html minifiers. You can use any minifier library which work for you but here I will use the minifiers available in ServiceStack source. Here are the html, css and js minifiers,    

 

 

01public class BasicHtmlMinifier
02{
03    static Regex BetweenScriptTagsRegEx = new Regex(@"<script[^>]*>[\w|\t|\r|\W]*?</script>", RegexOptions.Compiled);
04    static Regex BetweenTagsRegex = new Regex(@"(?<=[^])\t{2,}|(?<=[>])\s{2,}(?=[<])|(?<=[>])\s{2,11}(?=[<])|(?=[\n])\s{2,}|(?=[\r])\s{2,}", RegexOptions.Compiled);
05    static Regex MatchBodyRegEx = new Regex(@"</body>", RegexOptions.Compiled);
06 
07    public static string MinifyHtml(string html)
08    {
09        if (html == null)
10            return html;
11 
12        var mymatch = BetweenScriptTagsRegEx.Matches(html);
13        html = BetweenScriptTagsRegEx.Replace(html, string.Empty);
14        html = BetweenTagsRegex.Replace(html, string.Empty);
15 
16        var str = string.Empty;
17        foreach (Match match in mymatch)
18        {
19            str += match.ToString();
20        }
21 
22        html = MatchBodyRegEx.Replace(html, str + "</body>");
23        return html;
24    }
25}

 

 

001public class JSMinifier
002{
003    const int EOF = -1;
004 
005    TextReader sr;
006    StringBuilder sb;
007    int theA;
008    int theB;
009    int theLookahead = EOF;
010 
011    public string Compress(string js)
012    {
013        using (sr = new StringReader(js))
014        {
015            sb = new StringBuilder();
016            jsmin();
017            return sb.ToString(); // return the minified string 
018        }
019    }
020 
021    public static string MinifyJs(string js) //removed the out file path 
022    {
023        return new JSMinifier().Compress(js);
024    }
025 
026    /* jsmin -- Copy the input to the output, deleting the characters which are
027            insignificant to JavaScript. Comments will be removed. Tabs will be
028            replaced with spaces. Carriage returns will be replaced with linefeeds.
029            Most spaces and linefeeds will be removed.
030    */
031    void jsmin()
032    {
033        theA = '\n';
034        action(3);
035        while (theA != EOF)
036        {
037            switch (theA)
038            {
039                case ' ':
040                    {
041                        if (isAlphanum(theB))
042                        {
043                            action(1);
044                        }
045                        else
046                        {
047                            action(2);
048                        }
049                        break;
050                    }
051                case '\n':
052                    {
053                        switch (theB)
054                        {
055                            case '{':
056                            case '[':
057                            case '(':
058                            case '+':
059                            case '-':
060                                {
061                                    action(1);
062                                    break;
063                                }
064                            case ' ':
065                                {
066                                    action(3);
067                                    break;
068                                }
069                            default:
070                                {
071                                    if (isAlphanum(theB))
072                                    {
073                                        action(1);
074                                    }
075                                    else
076                                    {
077                                        action(2);
078                                    }
079                                    break;
080                                }
081                        }
082                        break;
083                    }
084                default:
085                    {
086                        switch (theB)
087                        {
088                            case ' ':
089                                {
090                                    if (isAlphanum(theA))
091                                    {
092                                        action(1);
093                                        break;
094                                    }
095                                    action(3);
096                                    break;
097                                }
098                            case '\n':
099                                {
100                                    switch (theA)
101                                    {
102                                        case '}':
103                                        case ']':
104                                        case ')':
105                                        case '+':
106                                        case '-':
107                                        case '"':
108                                        case '\'':
109                                            {
110                                                action(1);
111                                                break;
112                                            }
113                                        default:
114                                            {
115                                                if (isAlphanum(theA))
116                                                {
117                                                    action(1);
118                                                }
119                                                else
120                                                {
121                                                    action(3);
122                                                }
123                                                break;
124                                            }
125                                    }
126                                    break;
127                                }
128                            default:
129                                {
130                                    action(1);
131                                    break;
132                                }
133                        }
134                        break;
135                    }
136            }
137        }
138    }
139    /* action -- do something! What you do is determined by the argument:
140            1   Output A. Copy B to A. Get the next B.
141            2   Copy B to A. Get the next B. (Delete A).
142            3   Get the next B. (Delete B).
143       action treats a string as a single character. Wow!
144       action recognizes a regular expression if it is preceded by ( or , or =.
145    */
146    void action(int d)
147    {
148        if (d <= 1)
149        {
150            put(theA);
151        }
152        if (d <= 2)
153        {
154            theA = theB;
155            if (theA == '\'' || theA == '"')
156            {
157                for (;;)
158                {
159                    put(theA);
160                    theA = get();
161                    if (theA == theB)
162                    {
163                        break;
164                    }
165                    if (theA <= '\n')
166                    {
167                        throw new Exception(string.Format("Error: JSMIN unterminated string literal: {0}\n", theA));
168                    }
169                    if (theA == '\\')
170                    {
171                        put(theA);
172                        theA = get();
173                    }
174                }
175            }
176        }
177        if (d <= 3)
178        {
179            theB = next();
180            if (theB == '/' && (theA == '(' || theA == ',' || theA == '=' ||
181                                theA == '[' || theA == '!' || theA == ':' ||
182                                theA == '&' || theA == '|' || theA == '?' ||
183                                theA == '{' || theA == '}' || theA == ';' ||
184                                theA == '\n'))
185            {
186                put(theA);
187                put(theB);
188                for (;;)
189                {
190                    theA = get();
191                    if (theA == '/')
192                    {
193                        break;
194                    }
195                    else if (theA == '\\')
196                    {
197                        put(theA);
198                        theA = get();
199                    }
200                    else if (theA <= '\n')
201                    {
202                        throw new Exception(string.Format("Error: JSMIN unterminated Regular Expression literal : {0}.\n", theA));
203                    }
204                    put(theA);
205                }
206                theB = next();
207            }
208        }
209    }
210    /* next -- get the next character, excluding comments. peek() is used to see
211            if a '/' is followed by a '/' or '*'.
212    */
213    int next()
214    {
215        int c = get();
216        if (c == '/')
217        {
218            switch (peek())
219            {
220                case '/':
221                    {
222                        for (;;)
223                        {
224                            c = get();
225                            if (c <= '\n')
226                            {
227                                return c;
228                            }
229                        }
230                    }
231                case '*':
232                    {
233                        get();
234                        for (;;)
235                        {
236                            switch (get())
237                            {
238                                case '*':
239                                    {
240                                        if (peek() == '/')
241                                        {
242                                            get();
243                                            return ' ';
244                                        }
245                                        break;
246                                    }
247                                case EOF:
248                                    {
249                                        throw new Exception("Error: JSMIN Unterminated comment.\n");
250                                    }
251                            }
252                        }
253                    }
254                default:
255                    {
256                        return c;
257                    }
258            }
259        }
260        return c;
261    }
262    /* peek -- get the next character without getting it.
263    */
264    int peek()
265    {
266        theLookahead = get();
267        return theLookahead;
268    }
269    /* get -- return the next character from stdin. Watch out for lookahead. If
270            the character is a control character, translate it to a space or
271            linefeed.
272    */
273    int get()
274    {
275        int c = theLookahead;
276        theLookahead = EOF;
277        if (c == EOF)
278        {
279            c = sr.Read();
280        }
281        if (c >= ' ' || c == '\n' || c == EOF)
282        {
283            return c;
284        }
285        if (c == '\r')
286        {
287            return '\n';
288        }
289        return ' ';
290    }
291 
292    void put(int c)
293    {
294        sb.Append((char)c);
295    }
296    /* isAlphanum -- return true if the character is a letter, digit, underscore,
297            dollar sign, or non-ASCII character.
298    */
299    bool isAlphanum(int c)
300    {
301        return ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
302                (c >= 'A' && c <= 'Z') || c == '_' || c == '$' || c == '\\' ||
303                c > 126);
304    }
305}

 

 

01public class CssMinifier
02{
03    public static string MinifyCss(string css)
04    {
05        css = Regex.Replace(css, @"[a-zA-Z]+#", "#");
06        css = Regex.Replace(css, @"[\n\r]+\s*", String.Empty);
07        css = Regex.Replace(css, @"\s+", " ");
08        css = Regex.Replace(css, @"\s?([:,;{}])\s?", "$1");
09        css = css.Replace(";}", "}");
10        css = Regex.Replace(css, @"([\s:]0)(px|pt|%|em)", "$1");
11 
12        // Remove comments from CSS
13        css = Regex.Replace(css, @"/\[\d\D]?\*/", String.Empty);
14 
15        return css;
16    }
17}

 

 

                    BasicHtmlMinifier.MinifyHtml will minify the html, JsMinifier.MinifyJs will minify javascript and CssMinifier.MinifyCss will minify css. Now let's add our html, script and style tag helpers,

 

 

01public class HtmlTagHelper : TagHelper
02{
03    public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
04    {
05        if (context == null)
06        {
07            throw new ArgumentNullException(nameof(context));
08        }
09 
10        if (output == null)
11        {
12            throw new ArgumentNullException(nameof(output));
13        }
14 
15        var html = await output.GetChildContentAsync();
16        var minifiedHtml = BasicHtmlMinifier.MinifyHtml(html.GetContent());
17        output.Content.SetHtmlContent(minifiedHtml);
18    }
19}

 

 

01public class ScriptTagHelper : TagHelper
02{
03    public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
04    {
05        if (context == null)
06        {
07            throw new ArgumentNullException(nameof(context));
08        }
09 
10        if (output == null)
11        {
12            throw new ArgumentNullException(nameof(output));
13        }
14 
15        var js = (await output.GetChildContentAsync()).GetContent();
16        if (!string.IsNullOrWhiteSpace(js))
17        {
18            var minifiedJs = JSMinifier.MinifyJs(js);
19            output.Content.SetHtmlContent(minifiedJs);
20        }
21    }
22}

 

 

01public class StyleTagHelper : TagHelper
02{
03    public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
04    {
05        if (context == null)
06        {
07            throw new ArgumentNullException(nameof(context));
08        }
09 
10        if (output == null)
11        {
12            throw new ArgumentNullException(nameof(output));
13        }
14 
15        var css = await output.GetChildContentAsync();
16        var minifiedCss = CssMinifier.MinifyCss(css.GetContent());
17        output.Content.SetHtmlContent(minifiedCss);
18    }
19}

 

                    These tag helpers simply get the inner/child contents of the tag, then minify and set the minified contents. Finally just add these tag helpers inside your razor/cshtml view(s) where you need to minify,

 

 

1@addTagHelper "YourNameSpace.HtmlTagHelper, YourProject"
2@addTagHelper "YourNameSpace.StyleTagHelper, YourProject"
3@addTagHelper "YourNameSpace.ScriptTagHelper, YourProject"

   

                    Now just run your application and see the view source, you will see the html, inline css and inline js are minified.  

 

 

        Summary:

 

                    In this article, I showed you how easily we can minify html, inline css and inline javascript using tag helpers (which makes this task very easy). I have used ServiceStack helpers to minify but you can use any minifier you like.

1 Comment

  • Could you please help me..i have a question related to your previous blog http://weblogs.asp.net/imranbaloch/chart-helper-in-asp-net-mvc-3-0-with-transparent-background. Using chart helper to create chart and render image to the view. I am sending a chart theme.I have added 3 series for example all are of type column and i would want to specify color for each. Is it possible to do it in Chart helper without taking a default color it is showing?

Comments have been disabled for this content.