minifyTemplate.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  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. const processASTNode = function(node,htmlOptions,cssOptions,svgOptions){
  71. if (node instanceof U2.AST_Template
  72. && node.tag
  73. && ['html', 'css', 'svg'].includes(node.tag.name)) {
  74. const templateContent = node.strings.map(s => s.replace(/[\n|\r]/gm, ' '))
  75. if (node.tag.name == "html") {
  76. const input = templateContent.join('${}');
  77. const output = htmlMinify(input, htmlOptions)
  78. node.strings = output.split("${}");
  79. } else if (node.tag.name == "css" && cssOptions) {
  80. if (templateContent.length > 1) {
  81. throw new Error("css literal cannot use template.\r\nMore info https://lit-element.polymer-project.org/api/modules/_lit_element_.html#css ")
  82. }
  83. const input = templateContent[0];
  84. const output = new CleanCSS(cssOptions).minify(input).styles
  85. node.strings = [output];
  86. } else if (node.tag.name == "svg" && svgOptions) {
  87. const token = 'validTokenMlet'
  88. const input = templateContent.join(token);
  89. // Parse XML
  90. let obj = convert.xml2js(input)
  91. // Transform elements containing template to CData elt
  92. obj = toCData(obj, token)
  93. // Optimise svg (it does not affect Cdata elements)
  94. let xml = convert.js2xml(obj);
  95. xml = svgo.optimize(xml, svgOptions).data
  96. // Revert CData elt to their original form
  97. obj = convert.xml2js(xml)
  98. obj = fromCData(obj, token)
  99. // Reconstruct template
  100. node.strings = convert.js2xml(obj).split(token);
  101. }
  102. }
  103. }
  104. module.exports = function (code, options) {
  105. let mergedOptions = { ...defaultOptions, ...options }
  106. let cssOptions = false;
  107. if (mergedOptions.minifyCSS) {
  108. cssOptions = typeof mergedOptions.minifyCSS == 'object' ? mergedOptions.minifyCSS : {}
  109. }
  110. let svgOptions = false;
  111. if (mergedOptions.minifySVG) {
  112. svgOptions = mergedOptions.svgOptions ? mergedOptions.svgOptions : {};
  113. }
  114. const walker = new U2.TreeWalker((node)=>processASTNode(node,mergedOptions,cssOptions,svgOptions));
  115. const ast = U2.parse(code)
  116. ast.walk(walker)
  117. return ast.print_to_string({ beautify: false });;
  118. }