Attention: We have retired the ASP.NET Community Blogs. Learn more >

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,    

 

 

01 public 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 }

 

 

001 public 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 }

 

 

01 public 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,

 

 

01 public 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 }

 

 

01 public 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 }

 

 

01 public 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.