minifyTemplate.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. const U2 = require("uglify-js");
  2. const htmlMinify = require('html-minifier').minify;
  3. const CleanCSS = require('clean-css');
  4. const svgo = require('svgo');
  5. const convert = require('xml-js');
  6. defaultOptions = {
  7. collapseWhitespace: true,
  8. minifyCSS:true,
  9. minifySVG:true,
  10. svgOptions:{
  11. plugins: svgo.extendDefaultPlugins([
  12. {
  13. name: 'removeViewBox',
  14. active: false
  15. }
  16. ]),
  17. }
  18. }
  19. /**
  20. * Extract element containing token to Cdata element
  21. * @param {string} token
  22. * @param {XMLElement} elt
  23. * @returns {XMLElement} tranform XML tree by stringifying element containing the token
  24. */
  25. const toCData = function(elt,token){
  26. let transform = elt.name == token;
  27. if(elt.type=="text" && elt.text.includes(token)){
  28. transform = true
  29. }else if ("attributes" in elt ){
  30. for (const k of Object.keys(elt.attributes)) {
  31. if (k.includes(token)){
  32. transform = true;
  33. }else if(elt.attributes[k].includes(token)){
  34. transform = true
  35. }
  36. if(transform){
  37. break
  38. }
  39. }
  40. }
  41. if (transform){
  42. const newElt = {
  43. type:"cdata",
  44. cdata:token + JSON.stringify(elt)
  45. }
  46. return newElt
  47. }else{
  48. if ('elements' in elt){
  49. elt.elements = elt.elements.map(e=>toCData(e,token))
  50. }
  51. return elt
  52. }
  53. }
  54. /**
  55. * Recover extracted element containing token from Cdata element
  56. * @param {string} token
  57. * @param {XMLElement} elt
  58. * @returns {XMLElement} restored XML tree
  59. */
  60. const fromCData = function(elt,token){
  61. if (elt.type == "cdata" && elt.cdata.startsWith(token)){
  62. return JSON.parse(elt.cdata.substr(token.length))
  63. }else{
  64. if ('elements' in elt){
  65. elt.elements = elt.elements.map(e=>fromCData(e,token))
  66. }
  67. return elt
  68. }
  69. }
  70. module.exports = function (code, options){
  71. let mergedOptions = {...defaultOptions, ...options}
  72. const ast = U2.parse(code)
  73. let cssOptions = false;
  74. if (mergedOptions.minifyCSS){
  75. cssOptions = typeof mergedOptions.minifyCSS == 'object' ? mergedOptions.minifyCSS:{}
  76. }
  77. let svgOptions = false;
  78. if (mergedOptions.minifySVG){
  79. svgOptions = mergedOptions.svgOptions ? mergedOptions.svgOptions : {};
  80. }
  81. ast.walk(new U2.TreeWalker( (node)=>{
  82. if (node instanceof U2.AST_Template && node.tag && ['html','css','svg'].includes(node.tag.name)){
  83. const templateContent = node.strings.map(s=>s.replace(/[\n|\r]/gm, ' '))
  84. if(node.tag.name =="html"){
  85. const input = templateContent.join('${}');
  86. const output = htmlMinify(input, mergedOptions)
  87. node.strings = output.split("${}");
  88. } else if(node.tag.name == "css" && cssOptions){
  89. if (templateContent.length>1){
  90. throw new Error("css literal cannot use template.\r\nMore info https://lit-element.polymer-project.org/api/modules/_lit_element_.html#css ")
  91. }
  92. const input = templateContent[0];
  93. const output = new CleanCSS(cssOptions).minify(input).styles
  94. node.strings = [output];
  95. } else if(node.tag.name == "svg" && svgOptions){
  96. const token = 'validTokenMlet'
  97. const input = templateContent.join(token);
  98. // Parse XML
  99. let obj = convert.xml2js(input)
  100. // Transform elements containing template to CData elt
  101. obj = toCData(obj, token)
  102. // Optimise svg (it does not affect Cdata elements)
  103. let xml = convert.js2xml(obj);
  104. xml = svgo.optimize(xml, svgOptions).data
  105. // Revert CData elt to their original form
  106. obj = convert.xml2js(xml)
  107. obj = fromCData(obj,token)
  108. // Reconstruct template
  109. node.strings = convert.js2xml(obj).split(token);
  110. }
  111. }
  112. }))
  113. return ast.print_to_string({ beautify: false });;
  114. }