const U2 = require("uglify-js"); const htmlMinify = require('html-minifier').minify; const CleanCSS = require('clean-css'); const svgo = require('svgo'); const convert = require('xml-js'); defaultOptions = { collapseWhitespace: true, minifyCSS:true, minifySVG:true, svgOptions:{ plugins: svgo.extendDefaultPlugins([ { name: 'removeViewBox', active: false } ]), } } /** * Extract element containing token to Cdata element * @param {string} token * @param {XMLElement} elt * @returns {XMLElement} tranform XML tree by stringifying element containing the token */ const toCData = function(elt,token){ let transform = elt.name == token; if(elt.type=="text" && elt.text.includes(token)){ transform = true }else if ("attributes" in elt ){ for (const k of Object.keys(elt.attributes)) { if (k.includes(token)){ transform = true; }else if(elt.attributes[k].includes(token)){ transform = true } if(transform){ break } } } if (transform){ const newElt = { type:"cdata", cdata:token + JSON.stringify(elt) } return newElt }else{ if ('elements' in elt){ elt.elements = elt.elements.map(e=>toCData(e,token)) } return elt } } /** * Recover extracted element containing token from Cdata element * @param {string} token * @param {XMLElement} elt * @returns {XMLElement} restored XML tree */ const fromCData = function(elt,token){ if (elt.type == "cdata" && elt.cdata.startsWith(token)){ return JSON.parse(elt.cdata.substr(token.length)) }else{ if ('elements' in elt){ elt.elements = elt.elements.map(e=>fromCData(e,token)) } return elt } } module.exports = function (code, options){ let mergedOptions = {...defaultOptions, ...options} const ast = U2.parse(code) let cssOptions = false; if (mergedOptions.minifyCSS){ cssOptions = typeof mergedOptions.minifyCSS == 'object' ? mergedOptions.minifyCSS:{} } let svgOptions = false; if (mergedOptions.minifySVG){ svgOptions = mergedOptions.svgOptions ? mergedOptions.svgOptions : {}; } ast.walk(new U2.TreeWalker( (node)=>{ if (node instanceof U2.AST_Template && node.tag && ['html','css','svg'].includes(node.tag.name)){ const templateContent = node.strings.map(s=>s.replace(/[\n|\r]/gm, ' ')) if(node.tag.name =="html"){ const input = templateContent.join('${}'); const output = htmlMinify(input, mergedOptions) node.strings = output.split("${}"); } else if(node.tag.name == "css" && cssOptions){ if (templateContent.length>1){ throw new Error("css literal cannot use template.\r\nMore info https://lit-element.polymer-project.org/api/modules/_lit_element_.html#css ") } const input = templateContent[0]; const output = new CleanCSS(cssOptions).minify(input).styles node.strings = [output]; } else if(node.tag.name == "svg" && svgOptions){ const token = 'validTokenMlet' const input = templateContent.join(token); // Parse XML let obj = convert.xml2js(input) // Transform elements containing template to CData elt obj = toCData(obj, token) // Optimise svg (it does not affect Cdata elements) let xml = convert.js2xml(obj); xml = svgo.optimize(xml, svgOptions).data // Revert CData elt to their original form obj = convert.xml2js(xml) obj = fromCData(obj,token) // Reconstruct template node.strings = convert.js2xml(obj).split(token); } } })) return ast.print_to_string({ beautify: false });; }