When you work with HTML, CSS, and JavaScript, you often meet three versions on how those are stored in files – minified, formatted, somewhere in the middle (usually a total mess). I have all three versions in my PSWriteHTML module. Some are minified 3rd party resources, some are generated by my PowerShell commands (and are a total mess when it comes to formatting), and finally, some are formatted resources by using built-in VSCode features. In whatever form they are, they generally have no impact on how browsers display them. Browsers will read them in any kind and not care for how they look.
While formatting doesn't matter for browsers, it's much easier to debug, change HTML, CSS or JavaScript if it's properly formatted code. Trying to understand what happens in Minified JS script like the one below is far from optimal.
(function(){function main(){var tabButtons = [].slice.call(document.querySelectorAll("ul.tab-nav li a.buttonTab"));tabButtons.map(function(button){button.addEventListener("click",function(){document .querySelector("li a.active.buttonTab") .classList.remove("active");button.classList.add("active");document .querySelector(".tab-pane.active") .classList.remove("active");document .querySelector(button.getAttribute("href")) .classList.add("active")})})}if(document.readyState!== "loading"){main()}else{document.addEventListener("DOMContentLoaded",main)}})();
What if this could be formatted as code below?
(function() { function main() { var tabButtons = [].slice.call(document.querySelectorAll("ul.tab-nav li a.buttonTab")); tabButtons.map(function(button) { button.addEventListener("click", function() { document.querySelector("li a.active.buttonTab").classList.remove("active"); button.classList.add("active"); document.querySelector(".tab-pane.active").classList.remove("active"); document.querySelector(button.getAttribute("href")).classList.add("active") }) }) } if (document.readyState !== "loading") { main() } else { document.addEventListener("DOMContentLoaded", main) } })();
Much more beautiful, more comfortable to read and you can do some changes to it. Same goes for HTML. Often my PSWriteHTML module generates something like this:
<html><!-- HEADER --><head><meta charset="utf-8"/><meta content="width=device-width, initial-scale=1" name="viewport"/><meta name="author"/><meta content="2019-08-09 11:26:37" name="revised"/><title>My title</title><!-- CSS Default fonts START --><link href="https://fonts.googleapis.com/css?family=Roboto|Hammersmith+One|Questrial|Oswald" type="text/css" rel="stylesheet"/><!-- CSS Default fonts END --><!-- CSS Default fonts icons START --><link href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" type="text/css" rel="stylesheet"/><!-- CSS Default fonts icons END --><div class="flexElement overflowHidden"><table id="DT-hZRTQIVT" class="display compact"><thead><tr><th>Name</th><th class="none">Id</th><th class="none">HandleCount</th><th>WorkingSet</th></tr></thead><tbody><tr><td>1Password</td><td>22268</td><td>1007</td><td>87146496</td></tr><tr><td>aesm_service</td><td>25340</td><td>189</td><td>3948544</td></tr></tbody></table></div></div></div></div></body><!-- END BODY --><!-- FOOTER --><footer></footer><!-- END FOOTER --></html>
Which is hard to understand and modify. Let's check the formatted version:
<html> <!-- HEADER --> <head> <meta charset="utf-8"> <meta content="width=device-width, initial-scale=1" name="viewport"> <meta name="author"> <meta content="2019-08-09 11:26:37" name="revised"> <title>My title</title> <!-- CSS Default fonts START --> <link href="https://fonts.googleapis.com/css?family=Roboto|Hammersmith+One|Questrial|Oswald" type="text/css" rel="stylesheet"> <!-- CSS Default fonts END --> <!-- CSS Default fonts icons START --> <link href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" type="text/css" rel="stylesheet"> <!-- CSS Default fonts icons END --> </head> <body> <div class="flexElement overflowHidden"> <table id="DT-hZRTQIVT" class="display compact"> <thead> <tr> <th>Name</th> <th class="none">Id</th> <th class="none">HandleCount</th> <th>WorkingSet</th> </tr> </thead> <tbody> <tr> <td>1Password</td> <td>22268</td> <td>1007</td> <td>87146496</td> </tr> <tr> <td>aesm_service</td> <td>25340</td> <td>189</td> <td>3948544</td> </tr> </tbody> </table> </div> <footer></footer> <!-- END FOOTER --> </body> <!-- END BODY --> <!-- FOOTER --> </html>
Of course, you can do that formatting with VSCode, or you can use some online converter to do it for you, but I thought how hard it would be to do it in PowerShell?
Turns out with some help of NET libraries you can Format HTML, JavaScript, and CSS. You can also reverse that process and enable minification of those resources. Say hello to PSParseHTML. This little PowerShell module can do two things (for now).
It can do so thru six different functions
Let's see how this works when trying to format (prettify) HTML
$HTMLContent = '<html><!-- HEADER --><head><meta charset="utf-8"/><meta content="width=device-width, initial-scale=1" name="viewport"/><meta name="author"/><meta content="2019-08-09 11:26:37" name="revised"/><title>My title</title><!-- CSS Default fonts START --><link href="https://fonts.googleapis.com/css?family=Roboto|Hammersmith+One|Questrial|Oswald" type="text/css" rel="stylesheet"/><!-- CSS Default fonts END --><!-- CSS Default fonts icons START --><link href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" type="text/css" rel="stylesheet"/><!-- CSS Default fonts icons END --><div class="flexElement overflowHidden"><table id="DT-hZRTQIVT" class="display compact"><thead><tr><th>Name</th><th class="none">Id</th><th class="none">HandleCount</th><th>WorkingSet</th></tr></thead><tbody><tr><td>1Password</td><td>22268</td><td>1007</td><td>87146496</td></tr><tr><td>aesm_service</td><td>25340</td><td>189</td><td>3948544</td></tr></tbody></table></div></div></div></div></body><!-- END BODY --><!-- FOOTER --><footer></footer><!-- END FOOTER --></html>' Format-HTML -Content $HTMLContent
Output
<html> <!-- HEADER --> <head> <meta charset="utf-8"> <meta content="width=device-width, initial-scale=1" name="viewport"> <meta name="author"> <meta content="2019-08-09 11:26:37" name="revised"> <title>My title</title> <!-- CSS Default fonts START --> <link href="https://fonts.googleapis.com/css?family=Roboto|Hammersmith+One|Questrial|Oswald" type="text/css" rel="stylesheet"> <!-- CSS Default fonts END --> <!-- CSS Default fonts icons START --> <link href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" type="text/css" rel="stylesheet"> <!-- CSS Default fonts icons END --> </head> <body> <div class="flexElement overflowHidden"> <table id="DT-hZRTQIVT" class="display compact"> <thead> <tr> <th>Name</th> <th class="none">Id</th> <th class="none">HandleCount</th> <th>WorkingSet</th> </tr> </thead> <tbody> <tr> <td>1Password</td> <td>22268</td> <td>1007</td> <td>87146496</td> </tr> <tr> <td>aesm_service</td> <td>25340</td> <td>189</td> <td>3948544</td> </tr> </tbody> </table> </div> <footer></footer> <!-- END FOOTER --> </body> <!-- END BODY --> <!-- FOOTER --> </html>
Let's see how this works when trying to format (prettify) CSS
$CSS = '.tabsWrapper{text-align:center;margin:10px auto;font-family:"Roboto", sans-serif!important}.tabs{margin-top:10px;font-size:15px;padding:0;list-style:none;background:rgba(255, 255, 255, 1);box-shadow:0 5px 20px rgba(0, 0, 0, 0.1);border-radius:4px;position:relative}.tabs .round{border-radius:4px}.tabs a{text-decoration:none;color:rgba(119, 119, 119, 1);text-transform:uppercase;padding:10px 20px;display:inline-block;position:relative;z-index:1;transition-duration:0.6s}.tabs a.active{color:rgba(255, 255, 255, 1)}.tabs a i{margin-right:5px}.tabs .selector{display:none;height:100%;position:absolute;left:0;top:0;right:0;bottom:0;z-index:1;border-radius:4px}.tabs-content{display:none}.tabs-content.active{display:block}' Format-CSS -Content $CSS
Output
.tabsWrapper { text-align: center; margin: 10px auto; font-family: "Roboto", sans-serif !important } .tabs { margin-top: 10px; font-size: 15px; padding: 0; list-style: none; background: rgba(255, 255, 255, 1); box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1); border-radius: 4px; position: relative } .tabs .round { border-radius: 4px } .tabs a { text-decoration: none; color: rgba(119, 119, 119, 1); text-transform: uppercase; padding: 10px 20px; display: inline-block; position: relative; z-index: 1; transition-duration: 0.6s } .tabs a.active { color: rgba(255, 255, 255, 1) } .tabs a i { margin-right: 5px } .tabs .selector { display: none; height: 100%; position: absolute; left: 0; top: 0; right: 0; bottom: 0; z-index: 1; border-radius: 4px } .tabs-content { display: none } .tabs-content.active { display: block }
Let's see how this works when trying to format (prettify) JavaScript
$JSContent = '(function(){function main(){var tabButtons = [].slice.call(document.querySelectorAll("ul.tab-nav li a.buttonTab"));tabButtons.map(function(button){button.addEventListener("click",function(){document .querySelector("li a.active.buttonTab") .classList.remove("active");button.classList.add("active");document .querySelector(".tab-pane.active") .classList.remove("active");document .querySelector(button.getAttribute("href")) .classList.add("active")})})}if(document.readyState!== "loading"){main()}else{document.addEventListener("DOMContentLoaded",main)}})();' Format-JavaScript -Content $JSContent
Output
(function() { function main() { var tabButtons = [].slice.call(document.querySelectorAll("ul.tab-nav li a.buttonTab")); tabButtons.map(function(button) { button.addEventListener("click", function() { document.querySelector("li a.active.buttonTab").classList.remove("active"); button.classList.add("active"); document.querySelector(".tab-pane.active").classList.remove("active"); document.querySelector(button.getAttribute("href")).classList.add("active") }) }) } if (document.readyState !== "loading") { main() } else { document.addEventListener("DOMContentLoaded", main) } })();
Let's see how this works when trying to minify HTML
$HTMLContentFormatted = @" <html> <!-- HEADER --> <head> <meta charset="utf-8"> <meta content="width=device-width, initial-scale=1" name="viewport"> <meta name="author"> <meta content="2019-08-09 11:26:37" name="revised"> <title>My title</title> <!-- CSS Default fonts START --> <link href="https://fonts.googleapis.com/css?family=Roboto|Hammersmith+One|Questrial|Oswald" type="text/css" rel="stylesheet"> <!-- CSS Default fonts END --> <!-- CSS Default fonts icons START --> <link href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" type="text/css" rel="stylesheet"> <!-- CSS Default fonts icons END --> </head> <body> <div class="flexElement overflowHidden"> <table id="DT-hZRTQIVT" class="display compact"> <thead> <tr> <th>Name</th> <th class="none">Id</th> <th class="none">HandleCount</th> <th>WorkingSet</th> </tr> </thead> <tbody> <tr> <td>1Password</td> <td>22268</td> <td>1007</td> <td>87146496</td> </tr> <tr> <td>aesm_service</td> <td>25340</td> <td>189</td> <td>3948544</td> </tr> </tbody> </table> </div> <footer></footer> <!-- END FOOTER --> </body> <!-- END BODY --> <!-- FOOTER --> </html> "@ Optimize-HTML -Content $HTMLContentFormatted
Output
<html><head><meta charset=utf-8 /><meta content="width=device-width, initial-scale=1" name=viewport /><meta name=author /><meta content="2019-08-09 11:26:37" name=revised /><title>My title</title><link href="https://fonts.googleapis.com/css?family=Roboto|Hammersmith+One|Questrial|Oswald" type=text/css rel=stylesheet /><link href=https://use.fontawesome.com/releases/v5.7.2/css/all.css type=text/css rel=stylesheet /></head><body><div class="flexElement overflowHidden"><table id=DT-hZRTQIVT class="display compact"><thead><tr><th>Name</th><th class=none>Id</th><th class=none>HandleCount</th><th>WorkingSet</th></tr></thead><tbody><tr><td>1Password</td><td>22268</td><td>1007</td><td>87146496</td></tr><tr><td>aesm_service</td><td>25340</td><td>189</td><td>3948544</td></tr></tbody></table></div><footer></footer></body></html>
Good enough right?
Let's see how this works when trying to minify JavaScript (JS)
$JSCode = @" function openTab(evt, tabName) { // Declare all variables var i, tabcontent, tablinks; // Get all elements with class="tabcontent" and hide them tabcontent = document.getElementsByClassName("insideTab"); for (i = 0; i < tabcontent.length; i++) { tabcontent[i].style.display = "none"; } // Get all elements with class="tablinks" and remove the class "active" tablinks = document.getElementsByClassName("tablinks"); for (i = 0; i < tablinks.length; i++) { tablinks[i].className = tablinks[i].className.replace(" is-active", ""); } // Show the current tab, and add an "active" class to the link that opened the tab try { document.getElementById(tabName).style.display = "block"; } catch { // This error happens when someone forgot addng new TABS with proper names // Therefore style wouldn't exits on it. } evt.currentTarget.className += " active"; } "@ Optimize-JavaScript -Content $JSCode
Output
function openTab(n,t){for(var r,u=document.getElementsByClassName("insideTab"),i=0;i<u.length;i++)u[i].style.display="none";for(r=document.getElementsByClassName("tablinks"),i=0;i<r.length;i++)r[i].className=r[i].className.replace(" is-active","");try{document.getElementById(t).style.display="block"}catch({}){.currentTarget.className+=" active"}}
Let's see how this works when trying to minify CSS
$CssCode = @" .tabsWrapper { text-align: center; margin: 10px auto; font-family: 'Roboto', sans-serif !important; } .tabs { margin-top: 10px; font-size: 15px; padding: 0px; list-style: none; background: #fff; box-shadow: 0px 5px 20px rgba(0, 0, 0, 0.1); border-radius: 4px; position: relative; } .tabs .round { border-radius: 4px; } .tabs a { text-decoration: none; color: #777; text-transform: uppercase; padding: 10px 20px; display: inline-block; position: relative; z-index: 1; transition-duration: 0.6s; } .tabs a.active { color: #fff; } .tabs a i { margin-right: 5px; } .tabs .selector { display: none; height: 100%; position: absolute; left: 0px; top: 0px; right: 0px; bottom: 0px; z-index: 1; border-radius: 4px; background: ColorSelector; } .tabs-content { display: none; } .tabs-content.active { display: block; } "@ Optimize-CSS -Content $CssCode
Output
.tabsWrapper{text-align:center;margin:10px auto;font-family:"Roboto", sans-serif!important}.tabs{margin-top:10px;font-size:15px;padding:0;list-style:none;background:rgba(255, 255, 255, 1);box-shadow:0 5px 20px rgba(0, 0, 0, 0.1);border-radius:4px;position:relative}.tabs .round{border-radius:4px}.tabs a{text-decoration:none;color:rgba(119, 119, 119, 1);text-transform:uppercase;padding:10px 20px;display:inline-block;position:relative;z-index:1;transition-duration:0.6s}.tabs a.active{color:rgba(255, 255, 255, 1)}.tabs a i{margin-right:5px}.tabs .selector{display:none;height:100%;position:absolute;left:0;top:0;right:0;bottom:0;z-index:1;border-radius:4px}.tabs-content{display:none}.tabs-content.active{display:block}
As an added option there's also the seventh function called Convert-HTMLToText. It scans the whole HTML and removes all the tags.
$HTMLContentFormatted = @" <html> <!-- HEADER --> <head> <meta charset="utf-8"> <meta content="width=device-width, initial-scale=1" name="viewport"> <meta name="author"> <meta content="2019-08-09 11:26:37" name="revised"> <title>My title</title> <!-- CSS Default fonts START --> <link href="https://fonts.googleapis.com/css?family=Roboto|Hammersmith+One|Questrial|Oswald" type="text/css" rel="stylesheet"> <!-- CSS Default fonts END --> <!-- CSS Default fonts icons START --> <link href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" type="text/css" rel="stylesheet"> <!-- CSS Default fonts icons END --> </head> <body> <div class="flexElement overflowHidden"> <table id="DT-hZRTQIVT" class="display compact"> <thead> <tr> <th>Name</th> <th class="none">Id</th> <th class="none">HandleCount</th> <th>WorkingSet</th> </tr> </thead> <tbody> <tr> <td>1Password</td> <td>22268</td> <td>1007</td> <td>87146496</td> </tr> <tr> <td>aesm_service</td> <td>25340</td> <td>189</td> <td>3948544</td> </tr> </tbody> </table> </div> <footer></footer> <!-- END FOOTER --> </body> <!-- END BODY --> <!-- FOOTER --> </html> "@ Convert-HTMLToText -Content $HTMLContentFormatted
Output
Name Id HandleCount WorkingSet 1Password 22268 1007 87146496 aesm_service 25340 189 3948544
It's not super fancy, but it works. Probably could be replaced with some fancy Regex.
What you should know when using this module is that for now when you format HTML, it doesn't format inline CSS and JavaScript. It means while you get prettified HTML with tabs and all, but CSS and JavaScript will stay in the same state. I will try to find a way to optimize this either by using a different library or by doing a custom reading and doing some PowerShell magic. We'll see.
For easy use of PSParseHTML, you can install it from PowerShellGallery. Installing it is as easy as it gets.
Install-Module PSParseHTML -AllowClobber -Force
I may add few more things to this module (not yet sure what) and I do intend to integrate it with PSWriteHTML to either prettify or minify output depending on users needs. Not yet sure at what stage, which features but you can expect it to find it's way in there in one way or another. For now, you can use this in your projects as per your need.