|
|
|
const { minify } = require('html-minifier');
|
|
const cheerio = require('cheerio');
|
|
|
|
function sanitizeHtml(html) {
|
|
try {
|
|
|
|
html = html.replace(/<!--\[if.*?<!\[endif\]-->/gs, '');
|
|
|
|
|
|
html = html.replace(/src="https?:\/\/[^"]*\.{3}"/g, '');
|
|
|
|
|
|
html = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
|
|
|
|
return html;
|
|
} catch (err) {
|
|
console.warn('HTML sanitization failed:', err);
|
|
return html;
|
|
}
|
|
}
|
|
|
|
function validateHtml(html) {
|
|
try {
|
|
const $ = cheerio.load(html, {
|
|
xmlMode: false,
|
|
decodeEntities: false
|
|
});
|
|
|
|
const issues = [];
|
|
|
|
if ($('html').length === 0) issues.push('Missing html tag');
|
|
if ($('head').length === 0) issues.push('Missing head tag');
|
|
if ($('body').length === 0) issues.push('Missing body tag');
|
|
|
|
return issues;
|
|
} catch (err) {
|
|
console.warn('HTML validation failed:', err);
|
|
return ['Validation error: ' + err.message];
|
|
}
|
|
}
|
|
|
|
function safeMinify(html, options = {}) {
|
|
const defaultOptions = {
|
|
removeComments: true,
|
|
collapseWhitespace: true,
|
|
minifyCSS: true,
|
|
minifyJS: true,
|
|
conservativeCollapse: true,
|
|
keepClosingSlash: true,
|
|
removeAttributeQuotes: false,
|
|
removeEmptyAttributes: true,
|
|
removeRedundantAttributes: true,
|
|
removeScriptTypeAttributes: false,
|
|
removeStyleLinkTypeAttributes: false,
|
|
sortAttributes: true,
|
|
sortClassName: true
|
|
};
|
|
|
|
const minifyOptions = { ...defaultOptions, ...options };
|
|
|
|
try {
|
|
|
|
return minify(html, minifyOptions);
|
|
} catch (err) {
|
|
console.warn('Full minification failed, trying conservative mode:', err);
|
|
|
|
try {
|
|
|
|
return minify(html, {
|
|
removeComments: true,
|
|
collapseWhitespace: true,
|
|
conservativeCollapse: true,
|
|
keepClosingSlash: true,
|
|
removeAttributeQuotes: false,
|
|
removeEmptyAttributes: false,
|
|
removeRedundantAttributes: false
|
|
});
|
|
} catch (err2) {
|
|
console.error('Conservative minification also failed:', err2);
|
|
return html;
|
|
}
|
|
}
|
|
}
|
|
|
|
function minifyHtml(html, options = {}) {
|
|
const result = {
|
|
originalSize: html.length,
|
|
minifiedHtml: '',
|
|
success: false,
|
|
issues: [],
|
|
stats: {},
|
|
error: null
|
|
};
|
|
|
|
try {
|
|
|
|
const validationIssues = validateHtml(html);
|
|
result.issues = validationIssues;
|
|
|
|
|
|
const sanitized = sanitizeHtml(html);
|
|
|
|
|
|
const minified = safeMinify(sanitized, options);
|
|
result.minifiedHtml = minified;
|
|
result.success = true;
|
|
|
|
|
|
result.stats = {
|
|
originalSize: html.length,
|
|
minifiedSize: minified.length,
|
|
reduction: ((html.length - minified.length) / html.length * 100).toFixed(2) + '%',
|
|
validationIssues: validationIssues.length,
|
|
timestamp: new Date().toISOString()
|
|
};
|
|
|
|
} catch (err) {
|
|
result.error = {
|
|
message: err.message,
|
|
stack: err.stack
|
|
};
|
|
result.minifiedHtml = html;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
module.exports = {
|
|
minifyHtml,
|
|
validateHtml,
|
|
sanitizeHtml
|
|
}; |