const { JSDOM } = require('jsdom'); const fs = require('fs') const path = require('path') const request = require('request') const UglifyJS = require("uglify-js"); const CleanCSS = require('clean-css'); const minifyHtml = require('html-minifier').minify /** * 抽取所有脚本css等文件 * @param {Document} document jsdom */ function scriptFilter(document) { let ret = {script: [], link: []} for (let i = 0, dom = document.querySelectorAll('script'); i < dom.length; i++) { let src = dom[i].getAttribute('src') if (src || dom[i].innerHTML.trim().length > 0) { ret.script.push(src ? {content: src, type: 1, url: src} : {content: dom[i].innerHTML, type: 0, url: src} ) dom[i].parentNode.removeChild(dom[i]); } } for (let i = 0, dom = document.querySelectorAll('link[rel="stylesheet"],style'); i < dom.length; i++) { let tagName = dom[i].tagName ret.link.push(tagName === 'LINK' ? {content: dom[i].getAttribute('href'), type: 1} : {content: dom[i].innerHTML, type: 0} ) dom[i].parentNode.removeChild(dom[i]); } return ret } /** * 下载所有资源 * @param {String} dir 文件所在的路径 * @param {Array} contents 文件 */ async function downContent(dir, contents) { let labels = ['script', 'link'] for (let i = 0, label; label = labels[i]; i++) { for (let j = 0, item; item = contents[label][j]; j++) { if (item.type === 1) { let src = path.join(dir, item.content) src = ~src.indexOf('?') ? src.substring(0, src.indexOf('?')) : src if (fs.existsSync(src)) { item.content = fs.readFileSync(src).toString() } else { await new Promise(resolve => { request.get(item.content, (err, response) => { if (!err && response.statusCode === 200) { item.content = response.body } else { item.content = '' } resolve() }) }); } } } } return contents } /** * 合并资源,并压缩 * @param {JSON} contents 所有资源 */ function mergeStatic (contents) { let ret = {script: '', link: ''} Object.keys(ret).forEach(label => { contents[label].forEach(item => { ret[label] += item.content label === 'script' && (ret[label] += ';\n') }) }) let result = UglifyJS.minify(ret.script, { compress: { // booleans: false, // comparisons: false, // conditionals: false, // collapse_vars: false, // keep_fnames: true, // dead_code: false, // directives: false, // evaluate: false, hoist_props: false } }) if (result.error) { console.error('js有错误:', JSON.stringify(result.error)) } else { ret.script = result.code } ret.link = new CleanCSS({}).minify(ret.link).styles return ret } async function main(file, htmlName = 'minify-temp.html', jsName = 'minify-script.js', cssName = 'minify-style.css') { console.log('-------------------', process.cwd(), file, '--------------------') let src = path.join(process.cwd(), file) let document = new JSDOM(fs.readFileSync(src)).window.document let dir = path.join(src, '../') let contents = await downContent( path.join(src, '../'), scriptFilter(document) ) let ret = mergeStatic(contents) let $script = document.createElement('script') let $link = document.createElement('link') $script.setAttribute('src', 'js/' + jsName) $link.setAttribute('rel', 'stylesheet') $link.setAttribute('href', 'css/' + cssName) document.head.appendChild($link) document.body.appendChild($script) let html = ` ${document.documentElement.innerHTML} ` fs.writeFileSync(path.join(dir, 'js/' + jsName), '\ufeff' + ret.script) fs.writeFileSync(path.join(dir, 'css/' + cssName), '\ufeff' + ret.link) fs.writeFileSync(path.join(dir, htmlName), '\ufeff' + minifyHtml(html, { removeComments: true, collapseWhitespace: true, })) } main(...process.argv.splice(2)) // main('../CAD/index.html')