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 } } const processASTNode = function(node,htmlOptions,cssOptions,svgOptions){ 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, htmlOptions) 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); } } } module.exports = function (code, options) { let mergedOptions = { ...defaultOptions, ...options } let cssOptions = false; if (mergedOptions.minifyCSS) { cssOptions = typeof mergedOptions.minifyCSS == 'object' ? mergedOptions.minifyCSS : {} } let svgOptions = false; if (mergedOptions.minifySVG) { svgOptions = mergedOptions.svgOptions ? mergedOptions.svgOptions : {}; } const walker = new U2.TreeWalker((node)=>processASTNode(node,mergedOptions,cssOptions,svgOptions)); const ast = U2.parse(code) ast.walk(walker) return ast.print_to_string({ beautify: false });; }