|
#!/usr/bin/env node |
|
|
|
const { minify: minifyHtml } = require('html-minifier-terser'); |
|
const { minify: minifyJs } = require('terser'); |
|
const CleanCSS = require('clean-css'); |
|
const MarkdownIt = require('markdown-it'); |
|
const fs = require('fs'); |
|
const path = require('path'); |
|
|
|
|
|
const options = { |
|
collapseWhitespace: true, |
|
removeComments: true, |
|
removeEmptyAttributes: true, |
|
removeOptionalTags: true, |
|
removeRedundantAttributes: true, |
|
removeScriptTypeAttributes: true, |
|
removeStyleLinkTypeAttributes: true, |
|
minifyCSS: true, |
|
minifyJS: true, |
|
processScripts: ['application/json'], |
|
}; |
|
|
|
|
|
const cssOptions = { |
|
level: 2 |
|
}; |
|
|
|
|
|
async function minifyFile(inputPath, outputPath) { |
|
try { |
|
let ext = path.extname(inputPath).toLowerCase(); |
|
if (ext === '.md') ext = '.html'; |
|
const filename = path.basename(inputPath); |
|
let content = fs.readFileSync(inputPath, 'utf8'); |
|
let minified; |
|
|
|
|
|
if (filename.toLowerCase() === 'readme.md') { |
|
const md = new MarkdownIt({ |
|
html: true, |
|
linkify: true, |
|
typographer: true |
|
}); |
|
const readmeMdPath = path.join(__dirname, '..', 'README.md'); |
|
const markdownContent = fs.readFileSync(readmeMdPath, 'utf8'); |
|
|
|
const htmlContent = ` |
|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>README</title> |
|
<style> |
|
body { |
|
max-width: 800px; |
|
margin: 0 auto; |
|
padding: 20px; |
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; |
|
line-height: 1.6; |
|
} |
|
pre { |
|
background-color: #f6f8fa; |
|
padding: 16px; |
|
border-radius: 6px; |
|
overflow: auto; |
|
} |
|
code { |
|
background-color: #f6f8fa; |
|
padding: 0.2em 0.4em; |
|
border-radius: 3px; |
|
} |
|
img { |
|
max-width: 100%; |
|
} |
|
table { |
|
border-collapse: collapse; |
|
width: 100%; |
|
} |
|
table td, table th { |
|
border: 1px solid #dfe2e5; |
|
padding: 6px 13px; |
|
} |
|
blockquote { |
|
border-left: 4px solid #dfe2e5; |
|
margin: 0; |
|
padding: 0 1em; |
|
color: #6a737d; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
${md.render(markdownContent)} |
|
</body> |
|
</html> |
|
`; |
|
content = htmlContent; |
|
} |
|
|
|
switch (ext) { |
|
case '.html': |
|
minified = await minifyHtml(content, options); |
|
break; |
|
case '.js': |
|
const result = await minifyJs(content); |
|
minified = result.code; |
|
break; |
|
case '.css': |
|
minified = new CleanCSS(cssOptions).minify(content).styles; |
|
break; |
|
default: |
|
throw new Error(`Unsupported file type: ${ext}`); |
|
} |
|
|
|
fs.writeFileSync(outputPath, minified); |
|
console.log(`✓ Minified ${path.basename(inputPath)} -> ${path.basename(outputPath)}`); |
|
} catch (err) { |
|
console.error(`✗ Error processing ${inputPath}:`, err); |
|
process.exit(1); |
|
} |
|
} |
|
|
|
|
|
async function main() { |
|
|
|
const files = process.argv.slice(2); |
|
|
|
if (files.length === 0) { |
|
console.error('No input files specified'); |
|
process.exit(1); |
|
} |
|
|
|
const staticDir = path.join(__dirname, '..', 'static'); |
|
|
|
for (const file of files) { |
|
|
|
let inputPath; |
|
let outputPath; |
|
|
|
if (file.toLowerCase() === 'readme.md') { |
|
inputPath = path.join(__dirname, '..', 'README.md'); |
|
outputPath = path.join(staticDir, 'readme.min.html'); |
|
} else { |
|
inputPath = path.join(staticDir, file); |
|
const ext = path.extname(file); |
|
outputPath = path.join( |
|
staticDir, |
|
file.replace(ext, `.min${ext}`) |
|
); |
|
} |
|
|
|
await minifyFile(inputPath, outputPath); |
|
} |
|
} |
|
|
|
main(); |