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 });;
}