From 83484f0e473525a3ff866d1bb4e86d25299e31de Mon Sep 17 00:00:00 2001 From: wherewhere Date: Mon, 2 Dec 2024 16:48:09 +0800 Subject: [PATCH 01/11] feat: split filter --- .gitignore | 1 + index.js | 117 +++++++++++------ lib/css.js | 30 +++++ lib/filter.js | 344 ++------------------------------------------------ lib/html.js | 31 +++++ lib/js.js | 40 ++++++ lib/json.js | 36 ++++++ lib/svg.js | 45 +++++++ lib/tools.js | 72 +++++++++++ lib/xml.js | 37 ++++++ lib/zlib.js | 74 +++++++++++ lib/zstd.js | 40 ++++++ 12 files changed, 491 insertions(+), 376 deletions(-) create mode 100644 lib/css.js create mode 100644 lib/html.js create mode 100644 lib/js.js create mode 100644 lib/json.js create mode 100644 lib/svg.js create mode 100644 lib/tools.js create mode 100644 lib/xml.js create mode 100644 lib/zlib.js create mode 100644 lib/zstd.js diff --git a/.gitignore b/.gitignore index 22bded3..33581c3 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ tmp/ *.log coverage/ bun.lockb +*.bak diff --git a/index.js b/index.js index 5294e56..95d17ff 100644 --- a/index.js +++ b/index.js @@ -1,12 +1,13 @@ /* global hexo */ 'use strict' -hexo.config.minify = Object.assign({ +hexo.config.minify = { enable: true, - previewServer: true -}, hexo.config.minify) + previewServer: true, + ...hexo.config.minify +} -hexo.config.minify.html = Object.assign({ +hexo.config.minify.html = { enable: true, priority: 10, verbose: false, @@ -21,19 +22,21 @@ hexo.config.minify.html = Object.assign({ removeStyleLinkTypeAttributes: true, minifyJS: true, minifyCSS: true, - globOptions: { basename: true } -}, hexo.config.minify.html) + globOptions: { basename: true }, + ...hexo.config.minify.html +} -hexo.config.minify.css = Object.assign({ +hexo.config.minify.css = { enable: true, priority: 10, verbose: false, exclude: ['*.min.css'], level: 2, - globOptions: { basename: true } -}, hexo.config.minify.css) + globOptions: { basename: true }, + ...hexo.config.minify.css +} -hexo.config.minify.js = Object.assign({ +hexo.config.minify.js = { enable: true, priority: 10, verbose: false, @@ -41,68 +44,100 @@ hexo.config.minify.js = Object.assign({ compress: {}, mangle: true, output: {}, - globOptions: { basename: true } -}, hexo.config.minify.js) + globOptions: { basename: true }, + ...hexo.config.minify.js +} -hexo.config.minify.svg = Object.assign({ +hexo.config.minify.svg = { enable: true, priority: 10, verbose: false, include: ['*.svg', '!*.min.svg'], plugins: {}, - globOptions: { basename: true } -}, hexo.config.minify.svg) + globOptions: { basename: true }, + ...hexo.config.minify.svg +} -hexo.config.minify.gzip = Object.assign({ +hexo.config.minify.gzip = { enable: true, priority: 10, verbose: false, include: ['*.html', '*.css', '*.js', '*.txt', '*.ttf', '*.atom', '*.stl', '*.xml', '*.svg', '*.eot', '*.json'], - globOptions: { basename: true } -}, hexo.config.minify.gzip) + globOptions: { basename: true }, + ...hexo.config.minify.gzip +} -hexo.config.minify.brotli = Object.assign({ +hexo.config.minify.brotli = { enable: true, priority: 10, verbose: false, include: ['*.html', '*.css', '*.js', '*.txt', '*.ttf', '*.atom', '*.stl', '*.xml', '*.svg', '*.eot', '*.json'], - globOptions: { basename: true } -}, hexo.config.minify.brotli) + globOptions: { basename: true }, + ...hexo.config.minify.brotli +} -hexo.config.minify.zstd = Object.assign({ +hexo.config.minify.zstd = { enable: false, priority: 10, verbose: false, include: ['*.html', '*.css', '*.js', '*.txt', '*.ttf', '*.atom', '*.stl', '*.xml', '*.svg', '*.eot', '*.json'], - globOptions: { basename: true } -}, hexo.config.minify.zstd) + globOptions: { basename: true }, + ...hexo.config.minify.zstd +} -hexo.config.minify.xml = Object.assign({ +hexo.config.minify.xml = { enable: false, priority: 10, verbose: false, include: ['*.xml', '!*.min.xml'], removeComments: true, - globOptions: { basename: true } -}, hexo.config.minify.xml) + globOptions: { basename: true }, + ...hexo.config.minify.xml +} -hexo.config.minify.json = Object.assign({ +hexo.config.minify.json = { enable: false, priority: 10, verbose: false, include: ['*.json', '!*.min.json'], - globOptions: { basename: true } -}, hexo.config.minify.json) + globOptions: { basename: true }, + ...hexo.config.minify.json +} if (hexo.config.minify.enable === true && !(hexo.config.minify.previewServer === true && ['s', 'server'].includes(hexo.env.cmd))) { - const filter = require('./lib/filter') - hexo.extend.filter.register('after_render:html', filter.minifyHtml, hexo.config.minify.html.priority) - hexo.extend.filter.register('after_render:css', filter.minifyCss, hexo.config.minify.css.priority) - hexo.extend.filter.register('after_render:js', filter.minifyJs, hexo.config.minify.js.priority) - hexo.extend.filter.register('after_generate', filter.minifySvg, hexo.config.minify.svg.priority) - hexo.extend.filter.register('after_generate', filter.gzipFn, hexo.config.minify.gzip.priority) - hexo.extend.filter.register('after_generate', filter.brotliFn, hexo.config.minify.brotli.priority) - hexo.extend.filter.register('after_generate', filter.zstdFn, hexo.config.minify.zstd.priority) - hexo.extend.filter.register('after_generate', filter.minifyXml, hexo.config.minify.xml.priority) - hexo.extend.filter.register('after_generate', filter.minifyJson, hexo.config.minify.json.priority) -} + if (hexo.config.minify.html.enable === true) { + hexo.extend.filter.register('after_render:html', require('./lib/html').minifyHtml, hexo.config.minify.html.priority) + } + if (hexo.config.minify.css.enable === true) { + hexo.extend.filter.register('after_render:css', require('./lib/css').minifyCss, hexo.config.minify.css.priority) + } + if (hexo.config.minify.js.enable === true) { + hexo.extend.filter.register('after_render:js', require('./lib/js').minifyJs, hexo.config.minify.js.priority) + } + if (hexo.config.minify.svg.enable === true) { + hexo.extend.filter.register('after_generate', require('./lib/svg').minifySvg, hexo.config.minify.svg.priority) + } + if (hexo.config.minify.gzip.enable || hexo.config.minify.brotli.enable) { + const zlib = require('./lib/zlib') + if (hexo.config.minify.gzip.enable === true) { + hexo.extend.filter.register('after_generate', zlib.gzipFn, hexo.config.minify.gzip.priority) + } + if (hexo.config.minify.brotli.enable === true) { + hexo.extend.filter.register('after_generate', zlib.brotliFn, hexo.config.minify.brotli.priority) + } + } + if (hexo.config.minify.zstd.enable === true) { + try { + hexo.extend.filter.register('after_generate', require('./lib/zstd').zstdFn, hexo.config.minify.zstd.priority) + } catch (ex) { + const log = hexo.log || console + log.warn(`ZSTD load failed. ${ex}`) + } + } + if (hexo.config.minify.xml.enable === true) { + hexo.extend.filter.register('after_generate', require('./lib/xml').minifyXml, hexo.config.minify.xml.priority) + } + if (hexo.config.minify.json.enable === true) { + hexo.extend.filter.register('after_generate', require('./lib/json').minifyJson, hexo.config.minify.json.priority) + } +} \ No newline at end of file diff --git a/lib/css.js b/lib/css.js new file mode 100644 index 0000000..1029c68 --- /dev/null +++ b/lib/css.js @@ -0,0 +1,30 @@ +'use strict' +const CleanCSS = require('clean-css') +const { isMatch, logFn } = require('./tools') + +/** + * @param {string} str + * @param {{ path: string }} data + */ +async function minifyCss(str, data) { + const hexo = this + const options = hexo.config.minify.css + if (!str) return str + + const { path } = data + const { exclude, globOptions, verbose } = options + + if (isMatch(path, exclude, globOptions)) return str + + try { + const { styles } = await new CleanCSS(options).minify(str) + if (verbose) logFn.call(this, str, styles, path, 'css') + return styles + } catch (err) { + throw new Error(`Path: ${path}\n${err}`) + } +} + +module.exports = { + minifyCss +} \ No newline at end of file diff --git a/lib/filter.js b/lib/filter.js index 0d17003..45b1c42 100644 --- a/lib/filter.js +++ b/lib/filter.js @@ -1,337 +1,11 @@ 'use strict' - -const { minify: htmlMinify } = require('html-minifier-terser') -const CleanCSS = require('clean-css') -const { minify: terserMinify } = require('terser') -const { optimize: svgOptimize } = require('svgo') -const zlib = require('zlib') -const { promisify } = require('util') -const gzip = promisify(zlib.gzip) -const br = promisify(zlib.brotliCompress) -const { minify: compressXml } = require('minify-xml') -const micromatch = require('micromatch') -const { compress: zstd } = require('@mongodb-js/zstd') - -const isMatch = (path = '', patterns = [], options = {}) => { - if (path && patterns) { - if (path.length && patterns.length) { - if (typeof patterns === 'string') patterns = [patterns] - for (const pattern of patterns) { - // disable basename if a pattern includes a slash - let { basename } = options - // only disable when basename is enabled - basename = basename && !pattern.includes('/') - if (micromatch.isMatch(path, pattern, { ...options, basename })) { - return true - } - } - } - } - return false -} - -const match = (paths = [], patterns = [], options = {}) => { - let input = paths - if (paths && patterns) { - if (paths.length && patterns.length) { - const output = [] - if (typeof patterns === 'string') patterns = [patterns] - const exclude = patterns.filter((pattern) => pattern.startsWith('!')) - const include = patterns.filter((pattern) => !pattern.startsWith('!')) - if (exclude.length) input = micromatch(paths, exclude, options) - if (include.length) { - for (const pattern of include) { - let { basename } = options - basename = basename && !pattern.includes('/') - const tmp = micromatch(input, pattern, { ...options, basename }) - if (tmp.length) output.push(...tmp) - } - return [...new Set(output)] - } - return input - } - } - return paths -} - -function logFn (original, minified, path, ext) { - const saved = ((original.length - minified.length) / original.length * 100).toFixed(2) - const log = this.log || console - log.log(`${ext}: ${path} [${saved}% saved]`) -} - -async function minifyHtml (str, data) { - const hexo = this - const options = hexo.config.minify.html - if (options.enable === false || !str) return - - const { path } = data - const { exclude, globOptions, verbose } = options - - // Return if a path matches exclusion pattern - if (isMatch(path, exclude, globOptions)) return str - - try { - const result = await htmlMinify(str, options) - if (verbose) logFn.call(this, str, result, path, 'html') - - return result - } catch (err) { - throw new Error(`Path: ${path}\n${err}`) - } -} - -async function minifyCss (str, data) { - const hexo = this - const options = hexo.config.minify.css - if (options.enable === false || !str) return - - const { path } = data - const { exclude, globOptions, verbose } = options - - if (isMatch(path, exclude, globOptions)) return str - - try { - const { styles } = await new CleanCSS(options).minify(str) - if (verbose) logFn.call(this, str, styles, path, 'css') - return styles - } catch (err) { - throw new Error(`Path: ${path}\n${err}`) - } -} - -async function minifyJs (str, data) { - const hexo = this - const options = hexo.config.minify.js - if (options.enable === false || !str) return - - const { path } = data - const { exclude, globOptions, verbose } = options - - if (isMatch(path, exclude, globOptions)) return str - - // Terser doesn't like unsupported options - const jsOptions = Object.assign({}, options) - delete jsOptions.enable - delete jsOptions.priority - delete jsOptions.verbose - // Old option, retained to avoid crash when upgrading to v4 - delete jsOptions.logger - delete jsOptions.exclude - delete jsOptions.globOptions - - try { - const { code } = await terserMinify(str, jsOptions) - if (verbose) logFn.call(this, str, code, path, 'js') - return code - } catch (err) { - throw new Error(`Path: ${path}\n${err}`) - } -} - -function minifySvg () { - const hexo = this - const options = hexo.config.minify.svg - if (options.enable === false) return - - const { route } = hexo - const routeList = route.list() - const { globOptions, include, verbose } = options - // const plugins = Array.isArray(options.plugins) ? extendDefaultPlugins(options.plugins) : extendDefaultPlugins([]) - const pluginCfg = Object.prototype.toString.call(options.plugins) === '[object Object]' ? { ...options.plugins } : {} - const plugins = [{ - name: 'preset-default', - params: { - overrides: pluginCfg - } - }] - - return Promise.all((match(routeList, include, globOptions)).map((path) => { - return new Promise((resolve, reject) => { - const assetPath = route.get(path) - let assetTxt = '' - assetPath.on('data', (chunk) => (assetTxt += chunk)) - assetPath.on('end', async () => { - if (assetTxt.length) { - try { - const { data } = svgOptimize(assetTxt, { ...options, plugins }) - if (verbose) logFn.call(this, assetTxt, data, path, 'svg') - resolve(route.set(path, data)) - } catch (err) { - reject(new Error(`Path: ${path}\n${err}`)) - } - } - resolve() - }) - }) - })) -} - -function gzipFn () { - const hexo = this - const options = hexo.config.minify.gzip - if (options.enable === false) return - - const { route } = hexo - const routeList = route.list() - const { globOptions, include, verbose } = options - let { level } = options - if (typeof level !== 'number') level = zlib.constants.Z_BEST_COMPRESSION - - return Promise.all((match(routeList, include, globOptions)).map((path) => { - return new Promise((resolve, reject) => { - const assetPath = route.get(path) - let assetTxt = '' - assetPath.on('data', (chunk) => (assetTxt += chunk)) - assetPath.on('end', async () => { - if (assetTxt.length) { - try { - const result = await gzip(assetTxt, { level }) - if (verbose) logFn.call(this, assetTxt, result, path, 'gzip') - resolve(route.set(path + '.gz', result)) - } catch (err) { - reject(new Error(`Path: ${path}\n${err}`)) - } - } - resolve() - }) - }) - })) -} - -function brotliFn () { - const hexo = this - const options = hexo.config.minify.brotli - if (options.enable === false) return - - const { route } = hexo - const routeList = route.list() - const { globOptions, include, verbose } = options - let { level } = options - if (typeof level !== 'number') level = zlib.constants.BROTLI_MAX_QUALITY - - return Promise.all((match(routeList, include, globOptions)).map((path) => { - return new Promise((resolve, reject) => { - const assetPath = route.get(path) - let assetTxt = '' - assetPath.on('data', (chunk) => (assetTxt += chunk)) - assetPath.on('end', async () => { - if (assetTxt.length) { - try { - const result = await br(assetTxt, { params: { [zlib.constants.BROTLI_PARAM_QUALITY]: level } }) - if (verbose) logFn.call(this, assetTxt, result, path, 'brotli') - resolve(route.set(path + '.br', result)) - } catch (err) { - reject(new Error(`Path: ${path}\n${err}`)) - } - } - resolve() - }) - }) - })) -} - -function zstdFn () { - const hexo = this - const options = hexo.config.minify.zstd - if (options.enable === false) return - - const { route } = hexo - const routeList = route.list() - const { globOptions, include, verbose } = options - let { level } = options - if (typeof level !== 'number') level = undefined - - return Promise.all((match(routeList, include, globOptions)).map((path) => { - return new Promise((resolve, reject) => { - const assetPath = route.get(path) - let assetTxt = '' - assetPath.on('data', (chunk) => (assetTxt += chunk)) - assetPath.on('end', async () => { - if (assetTxt.length) { - try { - const input = Buffer.from(assetTxt, 'utf-8') - const result = await zstd(input, level) - if (verbose) logFn.call(this, assetTxt, result, path, 'zstd') - resolve(route.set(path + '.zst', result)) - } catch (err) { - reject(new Error(`Path: ${path}\n${err}`)) - } - } - resolve() - }) - }) - })) -} - -function minifyXml () { - const hexo = this - const options = hexo.config.minify.xml - if (options.enable === false) return - - const { route } = hexo - const routeList = route.list() - const { globOptions, include, verbose } = options - - return Promise.all((match(routeList, include, globOptions)).map((path) => { - return new Promise((resolve, reject) => { - const assetPath = route.get(path) - let assetTxt = '' - assetPath.on('data', (chunk) => (assetTxt += chunk)) - assetPath.on('end', () => { - if (assetTxt.length) { - try { - const result = compressXml(assetTxt, { ...options }) - if (verbose) logFn.call(this, assetTxt, result, path, 'xml') - resolve(route.set(path, result)) - } catch (err) { - reject(new Error(`Path: ${path}\n${err}`)) - } - } - resolve() - }) - }) - })) -} - -function minifyJson () { - const hexo = this - const options = hexo.config.minify.json - if (options.enable === false) return - - const { route } = hexo - const routeList = route.list() - const { globOptions, include, verbose } = options - - return Promise.all((match(routeList, include, globOptions)).map((path) => { - return new Promise((resolve, reject) => { - const assetPath = route.get(path) - let assetTxt = '' - assetPath.on('data', (chunk) => (assetTxt += chunk)) - assetPath.on('end', () => { - if (assetTxt.length) { - try { - const result = JSON.stringify(JSON.parse(assetTxt)) - if (verbose) logFn.call(this, assetTxt, result, path, 'json') - resolve(route.set(path, result)) - } catch (err) { - reject(new Error(`Path: ${path}\n${err}`)) - } - } - resolve() - }) - }) - })) -} - module.exports = { - minifyHtml, - minifyCss, - minifyJs, - minifySvg, - gzipFn, - brotliFn, - zstdFn, - minifyXml, - minifyJson -} + ... require('./html'), + ... require('./css'), + ... require('./js'), + ... require('./svg'), + ... require('./zlib'), + ... require('./zstd'), + ... require('./xml'), + ... require('./json'), +} \ No newline at end of file diff --git a/lib/html.js b/lib/html.js new file mode 100644 index 0000000..7ff5e12 --- /dev/null +++ b/lib/html.js @@ -0,0 +1,31 @@ +'use strict' +const { minify: htmlMinify } = require('html-minifier-terser') +const { isMatch, logFn } = require('./tools') + +/** + * @param {string} str + * @param {{ path: string }} data + */ +async function minifyHtml(str, data) { + const hexo = this + const options = hexo.config.minify.html + if (!str) return str + + const { path } = data + const { exclude, globOptions, verbose } = options + + // Return if a path matches exclusion pattern + if (isMatch(path, exclude, globOptions)) return str + + try { + const result = await htmlMinify(str, options) + if (verbose) logFn.call(this, str, result, path, 'html') + return result + } catch (err) { + throw new Error(`Path: ${path}\n${err}`) + } +} + +module.exports = { + minifyHtml +} \ No newline at end of file diff --git a/lib/js.js b/lib/js.js new file mode 100644 index 0000000..6947b56 --- /dev/null +++ b/lib/js.js @@ -0,0 +1,40 @@ +'use strict' +const { minify: terserMinify } = require('terser') +const { isMatch, logFn } = require('./tools') + +/** + * @param {string} str + * @param {{ path: string }} data + */ +async function minifyJs(str, data) { + const hexo = this + const options = hexo.config.minify.js + if (!str) return str + + const { path } = data + const { exclude, globOptions, verbose } = options + + if (isMatch(path, exclude, globOptions)) return str + + // Terser doesn't like unsupported options + const jsOptions = { ...options } + delete jsOptions.enable + delete jsOptions.priority + delete jsOptions.verbose + // Old option, retained to avoid crash when upgrading to v4 + delete jsOptions.logger + delete jsOptions.exclude + delete jsOptions.globOptions + + try { + const { code } = await terserMinify(str, jsOptions) + if (verbose) logFn.call(this, str, code, path, 'js') + return code + } catch (err) { + throw new Error(`Path: ${path}\n${err}`) + } +} + +module.exports = { + minifyJs +} \ No newline at end of file diff --git a/lib/json.js b/lib/json.js new file mode 100644 index 0000000..3464165 --- /dev/null +++ b/lib/json.js @@ -0,0 +1,36 @@ +'use strict' +const { match, logFn } = require('./tools') + +function minifyJson() { + const hexo = this + const options = hexo.config.minify.json + + const { route } = hexo + /** @type {string[]} */ + const routeList = route.list() + const { globOptions, include, verbose } = options + + return Promise.all((match(routeList, include, globOptions)).map(path => { + return new Promise((/** @type {(value: string) => void} */ resolve, reject) => { + const assetPath = route.get(path) + let assetTxt = '' + assetPath.on('data', chunk => (assetTxt += chunk)) + assetPath.on('end', () => { + if (assetTxt.length) { + try { + const result = JSON.stringify(JSON.parse(assetTxt)) + if (verbose) logFn.call(this, assetTxt, result, path, 'json') + resolve(route.set(path, result)) + } catch (err) { + reject(new Error(`Path: ${path}\n${err}`)) + } + } + resolve(assetTxt) + }) + }) + })) +} + +module.exports = { + minifyJson +} \ No newline at end of file diff --git a/lib/svg.js b/lib/svg.js new file mode 100644 index 0000000..59d7814 --- /dev/null +++ b/lib/svg.js @@ -0,0 +1,45 @@ +'use strict' +const { optimize: svgOptimize } = require('svgo') +const { match, logFn } = require('./tools') + +function minifySvg() { + const hexo = this + const options = hexo.config.minify.svg + + const { route } = hexo + /** @type {string[]} */ + const routeList = route.list() + const { globOptions, include, verbose } = options + // const plugins = Array.isArray(options.plugins) ? extendDefaultPlugins(options.plugins) : extendDefaultPlugins([]) + const pluginCfg = Object.prototype.toString.call(options.plugins) === '[object Object]' ? { ...options.plugins } : {} + const plugins = [{ + name: 'preset-default', + params: { + overrides: pluginCfg + } + }] + + return Promise.all((match(routeList, include, globOptions)).map(path => { + return new Promise((/** @type {(value: string) => void} */ resolve, reject) => { + const assetPath = route.get(path) + let assetTxt = '' + assetPath.on('data', chunk => (assetTxt += chunk)) + assetPath.on('end', async () => { + if (assetTxt.length) { + try { + const { data } = svgOptimize(assetTxt, { ...options, plugins }) + if (verbose) logFn.call(this, assetTxt, data, path, 'svg') + resolve(route.set(path, data)) + } catch (err) { + reject(new Error(`Path: ${path}\n${err}`)) + } + } + resolve(assetTxt) + }) + }) + })) +} + +module.exports = { + minifySvg +} \ No newline at end of file diff --git a/lib/tools.js b/lib/tools.js new file mode 100644 index 0000000..d156c97 --- /dev/null +++ b/lib/tools.js @@ -0,0 +1,72 @@ +'use strict' +const micromatch = require('micromatch') + +/** + * @param {string | string[]} patterns + * @param {{ basename: string }} options + */ +function isMatch(path = '', patterns = [], options = {}) { + if (path && patterns) { + if (path.length && patterns.length) { + if (typeof patterns === 'string') patterns = [patterns] + for (const pattern of patterns) { + // disable basename if a pattern includes a slash + let { basename } = options + // only disable when basename is enabled + basename = basename && !pattern.includes('/') + if (micromatch.isMatch(path, pattern, { ...options, basename })) { + return true + } + } + } + } + return false +} + +/** + * @param {string[]} paths + * @param {string | string[]} patterns + * @param {{ basename: string }} options + */ +function match(paths = [], patterns = [], options = {}) { + let input = paths + if (paths && patterns) { + if (paths.length && patterns.length) { + /** @type {string[]} */ + const output = [] + if (typeof patterns === 'string') patterns = [patterns] + const exclude = patterns.filter(pattern => pattern.startsWith('!')) + const include = patterns.filter(pattern => !pattern.startsWith('!')) + if (exclude.length) input = micromatch(paths, exclude, options) + if (include.length) { + for (const pattern of include) { + let { basename } = options + basename = basename && !pattern.includes('/') + const tmp = micromatch(input, pattern, { ...options, basename }) + if (tmp.length) output.push(...tmp) + } + return [...new Set(output)] + } + return input + } + } + return paths +} + +/** + * @param {string} original + * @param {string} ext + * @param {string} minified + * @param {string} path + */ +function logFn(original, minified, path, ext) { + const saved = ((original.length - minified.length) / original.length * 100).toFixed(2) + const log = this.log || console + log.log(`${ext}: ${path} [${saved}% saved]`) +} + +module.exports = { + isMatch, + match, + logFn +} \ No newline at end of file diff --git a/lib/xml.js b/lib/xml.js new file mode 100644 index 0000000..3a504fc --- /dev/null +++ b/lib/xml.js @@ -0,0 +1,37 @@ +'use strict' +const { minify: compressXml } = require('minify-xml') +const { match, logFn } = require('./tools') + +function minifyXml() { + const hexo = this + const options = hexo.config.minify.xml + + const { route } = hexo + /** @type {string[]} */ + const routeList = route.list() + const { globOptions, include, verbose } = options + + return Promise.all((match(routeList, include, globOptions)).map(path => { + return new Promise((/** @type {(value: string) => void} */ resolve, reject) => { + const assetPath = route.get(path) + let assetTxt = '' + assetPath.on('data', chunk => (assetTxt += chunk)) + assetPath.on('end', () => { + if (assetTxt.length) { + try { + const result = compressXml(assetTxt, { ...options }) + if (verbose) logFn.call(this, assetTxt, result, path, 'xml') + resolve(route.set(path, result)) + } catch (err) { + reject(new Error(`Path: ${path}\n${err}`)) + } + } + resolve(assetTxt) + }) + }) + })) +} + +module.exports = { + minifyXml +} \ No newline at end of file diff --git a/lib/zlib.js b/lib/zlib.js new file mode 100644 index 0000000..6f5b510 --- /dev/null +++ b/lib/zlib.js @@ -0,0 +1,74 @@ +'use strict' +const zlib = require('zlib') +const { promisify } = require('util') +const gzip = promisify(zlib.gzip) +const br = promisify(zlib.brotliCompress) +const { match, logFn } = require('./tools') + +function gzipFn() { + const hexo = this + const options = hexo.config.minify.gzip + + const { route } = hexo + /** @type {string} */ + const routeList = route.list() + const { globOptions, include, verbose } = options + let { level } = options + if (typeof level !== 'number') level = zlib.constants.Z_BEST_COMPRESSION + + return Promise.all((match(routeList, include, globOptions)).map(path => { + return new Promise((/** @type {(value: string) => void} */ resolve, reject) => { + const assetPath = route.get(path) + let assetTxt = '' + assetPath.on('data', chunk => (assetTxt += chunk)) + assetPath.on('end', async () => { + if (assetTxt.length) { + try { + const result = await gzip(assetTxt, { level }) + if (verbose) logFn.call(this, assetTxt, result, path, 'gzip') + resolve(route.set(path + '.gz', result)) + } catch (err) { + reject(new Error(`Path: ${path}\n${err}`)) + } + } + resolve(assetTxt) + }) + }) + })) +} + +function brotliFn() { + const hexo = this + const options = hexo.config.minify.brotli + + const { route } = hexo + const routeList = route.list() + const { globOptions, include, verbose } = options + let { level } = options + if (typeof level !== 'number') level = zlib.constants.BROTLI_MAX_QUALITY + + return Promise.all((match(routeList, include, globOptions)).map(path => { + return new Promise((/** @type {(value: string) => void} */ resolve, reject) => { + const assetPath = route.get(path) + let assetTxt = '' + assetPath.on('data', chunk => (assetTxt += chunk)) + assetPath.on('end', async () => { + if (assetTxt.length) { + try { + const result = await br(assetTxt, { params: { [zlib.constants.BROTLI_PARAM_QUALITY]: level } }) + if (verbose) logFn.call(this, assetTxt, result, path, 'brotli') + resolve(route.set(path + '.br', result)) + } catch (err) { + reject(new Error(`Path: ${path}\n${err}`)) + } + } + resolve(assetTxt) + }) + }) + })) +} + +module.exports = { + gzipFn, + brotliFn +} \ No newline at end of file diff --git a/lib/zstd.js b/lib/zstd.js new file mode 100644 index 0000000..f5fe4f1 --- /dev/null +++ b/lib/zstd.js @@ -0,0 +1,40 @@ +'use strict' +const { compress: zstd } = require('@mongodb-js/zstd') +const { match, logFn } = require('./tools') + +function zstdFn() { + const hexo = this + const options = hexo.config.minify.zstd + + const { route } = hexo + /** @type {string[]} */ + const routeList = route.list() + const { globOptions, include, verbose } = options + let { level } = options + if (typeof level !== 'number') level = undefined + + return Promise.all((match(routeList, include, globOptions)).map(path => { + return new Promise((/** @type {(value: string) => void} */ resolve, reject) => { + const assetPath = route.get(path) + let assetTxt = '' + assetPath.on('data', chunk => (assetTxt += chunk)) + assetPath.on('end', async () => { + if (assetTxt.length) { + try { + const input = Buffer.from(assetTxt, 'utf-8') + const result = await zstd(input, level) + if (verbose) logFn.call(this, assetTxt, result, path, 'zstd') + resolve(route.set(path + '.zst', result)) + } catch (err) { + reject(new Error(`Path: ${path}\n${err}`)) + } + } + resolve(assetTxt) + }) + }) + })) +} + +module.exports = { + zstdFn +} \ No newline at end of file From ac31b9edaa0afd561699ee5ec28ae68744249d8c Mon Sep 17 00:00:00 2001 From: wherewhere Date: Tue, 3 Dec 2024 16:18:51 +0800 Subject: [PATCH 02/11] feat: register compression after minify --- README.md | 10 +++++--- index.js | 68 +++++++++++++++++++++++++-------------------------- lib/css.js | 4 +-- lib/filter.js | 4 +-- lib/html.js | 2 +- lib/js.js | 10 +++----- lib/json.js | 4 +-- lib/svg.js | 6 ++--- lib/tools.js | 4 +-- lib/xml.js | 2 +- lib/zlib.js | 8 +++--- lib/zstd.js | 4 +-- package.json | 4 +-- 13 files changed, 64 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index 732469c..39efae3 100644 --- a/README.md +++ b/README.md @@ -39,10 +39,11 @@ minify: css: js: svg: - gzip: - brotli: xml: json: + gzip: + brotli: + zstd: ``` - **enable** - Enable the plugin. Defaults to `true`. @@ -51,10 +52,11 @@ minify: - **css** - See [CSS](#css) section - **js** - See [JS](#js) section - **svg** - See [SVG](#svg) section -- **gzip** - See [Gzip](#gzip) section -- **brotli** - See [Brotli](#brotli) section - **xml** - See [XML](#xml) section - **json** - See [JSON](#json) section +- **gzip** - See [Gzip](#gzip) section +- **brotli** - See [Brotli](#brotli) section +- **zstd** - See [Zstd](#zstd) section ## HTML diff --git a/index.js b/index.js index 95d17ff..2b64661 100644 --- a/index.js +++ b/index.js @@ -58,33 +58,6 @@ hexo.config.minify.svg = { ...hexo.config.minify.svg } -hexo.config.minify.gzip = { - enable: true, - priority: 10, - verbose: false, - include: ['*.html', '*.css', '*.js', '*.txt', '*.ttf', '*.atom', '*.stl', '*.xml', '*.svg', '*.eot', '*.json'], - globOptions: { basename: true }, - ...hexo.config.minify.gzip -} - -hexo.config.minify.brotli = { - enable: true, - priority: 10, - verbose: false, - include: ['*.html', '*.css', '*.js', '*.txt', '*.ttf', '*.atom', '*.stl', '*.xml', '*.svg', '*.eot', '*.json'], - globOptions: { basename: true }, - ...hexo.config.minify.brotli -} - -hexo.config.minify.zstd = { - enable: false, - priority: 10, - verbose: false, - include: ['*.html', '*.css', '*.js', '*.txt', '*.ttf', '*.atom', '*.stl', '*.xml', '*.svg', '*.eot', '*.json'], - globOptions: { basename: true }, - ...hexo.config.minify.zstd -} - hexo.config.minify.xml = { enable: false, priority: 10, @@ -99,11 +72,38 @@ hexo.config.minify.json = { enable: false, priority: 10, verbose: false, - include: ['*.json', '!*.min.json'], + include: ['*.json', '*.webmanifest', '!*.min.json', '!*.min.webmanifest'], globOptions: { basename: true }, ...hexo.config.minify.json } +hexo.config.minify.gzip = { + enable: true, + priority: 10, + verbose: false, + include: ['*.html', '*.css', '*.js', '*.txt', '*.ttf', '*.atom', '*.stl', '*.xml', '*.svg', '*.eot', '*.json', '*.webmanifest'], + globOptions: { basename: true }, + ...hexo.config.minify.gzip +} + +hexo.config.minify.brotli = { + enable: true, + priority: 10, + verbose: false, + include: ['*.html', '*.css', '*.js', '*.txt', '*.ttf', '*.atom', '*.stl', '*.xml', '*.svg', '*.eot', '*.json', '*.webmanifest'], + globOptions: { basename: true }, + ...hexo.config.minify.brotli +} + +hexo.config.minify.zstd = { + enable: false, + priority: 10, + verbose: false, + include: ['*.html', '*.css', '*.js', '*.txt', '*.ttf', '*.atom', '*.stl', '*.xml', '*.svg', '*.eot', '*.json', '*.webmanifest'], + globOptions: { basename: true }, + ...hexo.config.minify.zstd +} + if (hexo.config.minify.enable === true && !(hexo.config.minify.previewServer === true && ['s', 'server'].includes(hexo.env.cmd))) { if (hexo.config.minify.html.enable === true) { hexo.extend.filter.register('after_render:html', require('./lib/html').minifyHtml, hexo.config.minify.html.priority) @@ -117,6 +117,12 @@ if (hexo.config.minify.enable === true && !(hexo.config.minify.previewServer === if (hexo.config.minify.svg.enable === true) { hexo.extend.filter.register('after_generate', require('./lib/svg').minifySvg, hexo.config.minify.svg.priority) } + if (hexo.config.minify.xml.enable === true) { + hexo.extend.filter.register('after_generate', require('./lib/xml').minifyXml, hexo.config.minify.xml.priority) + } + if (hexo.config.minify.json.enable === true) { + hexo.extend.filter.register('after_generate', require('./lib/json').minifyJson, hexo.config.minify.json.priority) + } if (hexo.config.minify.gzip.enable || hexo.config.minify.brotli.enable) { const zlib = require('./lib/zlib') if (hexo.config.minify.gzip.enable === true) { @@ -134,10 +140,4 @@ if (hexo.config.minify.enable === true && !(hexo.config.minify.previewServer === log.warn(`ZSTD load failed. ${ex}`) } } - if (hexo.config.minify.xml.enable === true) { - hexo.extend.filter.register('after_generate', require('./lib/xml').minifyXml, hexo.config.minify.xml.priority) - } - if (hexo.config.minify.json.enable === true) { - hexo.extend.filter.register('after_generate', require('./lib/json').minifyJson, hexo.config.minify.json.priority) - } } \ No newline at end of file diff --git a/lib/css.js b/lib/css.js index 1029c68..5fe78e7 100644 --- a/lib/css.js +++ b/lib/css.js @@ -11,13 +11,13 @@ async function minifyCss(str, data) { const options = hexo.config.minify.css if (!str) return str - const { path } = data + const path = data.path const { exclude, globOptions, verbose } = options if (isMatch(path, exclude, globOptions)) return str try { - const { styles } = await new CleanCSS(options).minify(str) + const styles = await new CleanCSS(options).minify(str).styles if (verbose) logFn.call(this, str, styles, path, 'css') return styles } catch (err) { diff --git a/lib/filter.js b/lib/filter.js index 45b1c42..9cc9099 100644 --- a/lib/filter.js +++ b/lib/filter.js @@ -4,8 +4,8 @@ module.exports = { ... require('./css'), ... require('./js'), ... require('./svg'), - ... require('./zlib'), - ... require('./zstd'), ... require('./xml'), ... require('./json'), + ... require('./zlib'), + ... require('./zstd') } \ No newline at end of file diff --git a/lib/html.js b/lib/html.js index 7ff5e12..bfcb758 100644 --- a/lib/html.js +++ b/lib/html.js @@ -11,7 +11,7 @@ async function minifyHtml(str, data) { const options = hexo.config.minify.html if (!str) return str - const { path } = data + const path = data.path const { exclude, globOptions, verbose } = options // Return if a path matches exclusion pattern diff --git a/lib/js.js b/lib/js.js index 6947b56..f32378c 100644 --- a/lib/js.js +++ b/lib/js.js @@ -11,23 +11,19 @@ async function minifyJs(str, data) { const options = hexo.config.minify.js if (!str) return str - const { path } = data - const { exclude, globOptions, verbose } = options + const path = data.path + const { exclude, globOptions, verbose, ...jsOptions } = options if (isMatch(path, exclude, globOptions)) return str // Terser doesn't like unsupported options - const jsOptions = { ...options } delete jsOptions.enable delete jsOptions.priority - delete jsOptions.verbose // Old option, retained to avoid crash when upgrading to v4 delete jsOptions.logger - delete jsOptions.exclude - delete jsOptions.globOptions try { - const { code } = await terserMinify(str, jsOptions) + const code = await terserMinify(str, jsOptions).code if (verbose) logFn.call(this, str, code, path, 'js') return code } catch (err) { diff --git a/lib/json.js b/lib/json.js index 3464165..cd83658 100644 --- a/lib/json.js +++ b/lib/json.js @@ -5,10 +5,10 @@ function minifyJson() { const hexo = this const options = hexo.config.minify.json - const { route } = hexo + const route = hexo.route /** @type {string[]} */ const routeList = route.list() - const { globOptions, include, verbose } = options + const { globOptions, include, verbose, useBigInt } = options return Promise.all((match(routeList, include, globOptions)).map(path => { return new Promise((/** @type {(value: string) => void} */ resolve, reject) => { diff --git a/lib/svg.js b/lib/svg.js index 59d7814..d1f60d6 100644 --- a/lib/svg.js +++ b/lib/svg.js @@ -6,12 +6,12 @@ function minifySvg() { const hexo = this const options = hexo.config.minify.svg - const { route } = hexo + const route = hexo.route /** @type {string[]} */ const routeList = route.list() const { globOptions, include, verbose } = options // const plugins = Array.isArray(options.plugins) ? extendDefaultPlugins(options.plugins) : extendDefaultPlugins([]) - const pluginCfg = Object.prototype.toString.call(options.plugins) === '[object Object]' ? { ...options.plugins } : {} + const pluginCfg = typeof options.plugins === 'object' ? { ...options.plugins } : {} const plugins = [{ name: 'preset-default', params: { @@ -27,7 +27,7 @@ function minifySvg() { assetPath.on('end', async () => { if (assetTxt.length) { try { - const { data } = svgOptimize(assetTxt, { ...options, plugins }) + const data = svgOptimize(assetTxt, { ...options, plugins }).data if (verbose) logFn.call(this, assetTxt, data, path, 'svg') resolve(route.set(path, data)) } catch (err) { diff --git a/lib/tools.js b/lib/tools.js index d156c97..2268fd1 100644 --- a/lib/tools.js +++ b/lib/tools.js @@ -11,7 +11,7 @@ function isMatch(path = '', patterns = [], options = {}) { if (typeof patterns === 'string') patterns = [patterns] for (const pattern of patterns) { // disable basename if a pattern includes a slash - let { basename } = options + let basename = options.basename // only disable when basename is enabled basename = basename && !pattern.includes('/') if (micromatch.isMatch(path, pattern, { ...options, basename })) { @@ -40,7 +40,7 @@ function match(paths = [], patterns = [], options = {}) { if (exclude.length) input = micromatch(paths, exclude, options) if (include.length) { for (const pattern of include) { - let { basename } = options + let basename = options.basename basename = basename && !pattern.includes('/') const tmp = micromatch(input, pattern, { ...options, basename }) if (tmp.length) output.push(...tmp) diff --git a/lib/xml.js b/lib/xml.js index 3a504fc..985e2df 100644 --- a/lib/xml.js +++ b/lib/xml.js @@ -6,7 +6,7 @@ function minifyXml() { const hexo = this const options = hexo.config.minify.xml - const { route } = hexo + const route = hexo.route /** @type {string[]} */ const routeList = route.list() const { globOptions, include, verbose } = options diff --git a/lib/zlib.js b/lib/zlib.js index 6f5b510..0b26a29 100644 --- a/lib/zlib.js +++ b/lib/zlib.js @@ -9,11 +9,11 @@ function gzipFn() { const hexo = this const options = hexo.config.minify.gzip - const { route } = hexo + const route = hexo.route /** @type {string} */ const routeList = route.list() const { globOptions, include, verbose } = options - let { level } = options + let level = options.level if (typeof level !== 'number') level = zlib.constants.Z_BEST_COMPRESSION return Promise.all((match(routeList, include, globOptions)).map(path => { @@ -41,10 +41,10 @@ function brotliFn() { const hexo = this const options = hexo.config.minify.brotli - const { route } = hexo + const route = hexo.route const routeList = route.list() const { globOptions, include, verbose } = options - let { level } = options + let level = options.level if (typeof level !== 'number') level = zlib.constants.BROTLI_MAX_QUALITY return Promise.all((match(routeList, include, globOptions)).map(path => { diff --git a/lib/zstd.js b/lib/zstd.js index f5fe4f1..435381b 100644 --- a/lib/zstd.js +++ b/lib/zstd.js @@ -6,11 +6,11 @@ function zstdFn() { const hexo = this const options = hexo.config.minify.zstd - const { route } = hexo + const route = hexo.route /** @type {string[]} */ const routeList = route.list() const { globOptions, include, verbose } = options - let { level } = options + let level = options.level if (typeof level !== 'number') level = undefined return Promise.all((match(routeList, include, globOptions)).map(path => { diff --git a/package.json b/package.json index 1ff3593..c1cebe9 100644 --- a/package.json +++ b/package.json @@ -26,13 +26,13 @@ "url": "git+https://github.com/curbengh/hexo-yam.git" }, "dependencies": { + "@mongodb-js/zstd": "^1.2.0", "clean-css": "^5.1.2", "html-minifier-terser": "^7.2.0", "micromatch": "^4.0.2", "minify-xml": "^3.2.0", "svgo": "^3.0.0", - "terser": "^5.3.0", - "@mongodb-js/zstd": "^1.2.0" + "terser": "^5.3.0" }, "devDependencies": { "hexo": "^7.1.0", From bcaa327494900550743c1d8006b4ab9c227b43c6 Mon Sep 17 00:00:00 2001 From: wherewhere Date: Tue, 3 Dec 2024 16:41:52 +0800 Subject: [PATCH 03/11] feat: add .webmanifest as json, add .map and .wasm to compressing --- README.md | 11 +++++++++++ index.js | 6 +++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 39efae3..9343c10 100644 --- a/README.md +++ b/README.md @@ -177,7 +177,9 @@ minify: enable: false include: - "*.json" + - "*.webmanifest" - "!*.min.json" + - "!*.min.webmanifest" ``` - **enable** - Enable the plugin. Defaults to `false`. @@ -197,6 +199,8 @@ minify: - "*.html" - "*.css" - "*.js" + - "*.map" + - "*.wasm" - "*.txt" - "*.ttf" - "*.atom" @@ -205,6 +209,7 @@ minify: - "*.svg" - "*.eot" - "*.json" + - "*.webmanifest" ``` - **enable** - Enable the plugin. Defaults to `true`. @@ -226,6 +231,8 @@ minify: - "*.html" - "*.css" - "*.js" + - "*.map" + - "*.wasm" - "*.txt" - "*.ttf" - "*.atom" @@ -234,6 +241,7 @@ minify: - "*.svg" - "*.eot" - "*.json" + - "*.webmanifest" ``` - **enable** - Enable the plugin. Defaults to `true`. @@ -253,6 +261,8 @@ minify: - "*.html" - "*.css" - "*.js" + - "*.map" + - "*.wasm" - "*.txt" - "*.ttf" - "*.atom" @@ -261,6 +271,7 @@ minify: - "*.svg" - "*.eot" - "*.json" + - "*.webmanifest" ``` - **enable** - Enable the plugin. Defaults to `false`. diff --git a/index.js b/index.js index 2b64661..c9235be 100644 --- a/index.js +++ b/index.js @@ -81,7 +81,7 @@ hexo.config.minify.gzip = { enable: true, priority: 10, verbose: false, - include: ['*.html', '*.css', '*.js', '*.txt', '*.ttf', '*.atom', '*.stl', '*.xml', '*.svg', '*.eot', '*.json', '*.webmanifest'], + include: ['*.html', '*.css', '*.js', '*.map', '*.wasm', '*.txt', '*.ttf', '*.atom', '*.stl', '*.xml', '*.svg', '*.eot', '*.json', '*.webmanifest'], globOptions: { basename: true }, ...hexo.config.minify.gzip } @@ -90,7 +90,7 @@ hexo.config.minify.brotli = { enable: true, priority: 10, verbose: false, - include: ['*.html', '*.css', '*.js', '*.txt', '*.ttf', '*.atom', '*.stl', '*.xml', '*.svg', '*.eot', '*.json', '*.webmanifest'], + include: ['*.html', '*.css', '*.js', '*.map', '*.wasm', '*.txt', '*.ttf', '*.atom', '*.stl', '*.xml', '*.svg', '*.eot', '*.json', '*.webmanifest'], globOptions: { basename: true }, ...hexo.config.minify.brotli } @@ -99,7 +99,7 @@ hexo.config.minify.zstd = { enable: false, priority: 10, verbose: false, - include: ['*.html', '*.css', '*.js', '*.txt', '*.ttf', '*.atom', '*.stl', '*.xml', '*.svg', '*.eot', '*.json', '*.webmanifest'], + include: ['*.html', '*.css', '*.js', '*.map', '*.wasm', '*.txt', '*.ttf', '*.atom', '*.stl', '*.xml', '*.svg', '*.eot', '*.json', '*.webmanifest'], globOptions: { basename: true }, ...hexo.config.minify.zstd } From 83f62ea5189e772db699c65a5297def4c548a548 Mon Sep 17 00:00:00 2001 From: wherewhere Date: Tue, 3 Dec 2024 17:26:45 +0800 Subject: [PATCH 04/11] fix: function for after_generate return void --- lib/json.js | 8 ++++---- lib/svg.js | 6 +++--- lib/xml.js | 6 +++--- lib/zlib.js | 11 ++++++----- lib/zstd.js | 6 +++--- 5 files changed, 19 insertions(+), 18 deletions(-) diff --git a/lib/json.js b/lib/json.js index cd83658..287ceaf 100644 --- a/lib/json.js +++ b/lib/json.js @@ -8,10 +8,10 @@ function minifyJson() { const route = hexo.route /** @type {string[]} */ const routeList = route.list() - const { globOptions, include, verbose, useBigInt } = options + const { globOptions, include, verbose } = options return Promise.all((match(routeList, include, globOptions)).map(path => { - return new Promise((/** @type {(value: string) => void} */ resolve, reject) => { + return new Promise((/** @type {(value: void) => void} */ resolve, reject) => { const assetPath = route.get(path) let assetTxt = '' assetPath.on('data', chunk => (assetTxt += chunk)) @@ -20,12 +20,12 @@ function minifyJson() { try { const result = JSON.stringify(JSON.parse(assetTxt)) if (verbose) logFn.call(this, assetTxt, result, path, 'json') - resolve(route.set(path, result)) + route.set(path, result) } catch (err) { reject(new Error(`Path: ${path}\n${err}`)) } } - resolve(assetTxt) + resolve() }) }) })) diff --git a/lib/svg.js b/lib/svg.js index d1f60d6..f8d478a 100644 --- a/lib/svg.js +++ b/lib/svg.js @@ -20,7 +20,7 @@ function minifySvg() { }] return Promise.all((match(routeList, include, globOptions)).map(path => { - return new Promise((/** @type {(value: string) => void} */ resolve, reject) => { + return new Promise((/** @type {(value: void) => void} */ resolve, reject) => { const assetPath = route.get(path) let assetTxt = '' assetPath.on('data', chunk => (assetTxt += chunk)) @@ -29,12 +29,12 @@ function minifySvg() { try { const data = svgOptimize(assetTxt, { ...options, plugins }).data if (verbose) logFn.call(this, assetTxt, data, path, 'svg') - resolve(route.set(path, data)) + route.set(path, data) } catch (err) { reject(new Error(`Path: ${path}\n${err}`)) } } - resolve(assetTxt) + resolve() }) }) })) diff --git a/lib/xml.js b/lib/xml.js index 985e2df..264345d 100644 --- a/lib/xml.js +++ b/lib/xml.js @@ -12,7 +12,7 @@ function minifyXml() { const { globOptions, include, verbose } = options return Promise.all((match(routeList, include, globOptions)).map(path => { - return new Promise((/** @type {(value: string) => void} */ resolve, reject) => { + return new Promise((/** @type {(value: void) => void} */ resolve, reject) => { const assetPath = route.get(path) let assetTxt = '' assetPath.on('data', chunk => (assetTxt += chunk)) @@ -21,12 +21,12 @@ function minifyXml() { try { const result = compressXml(assetTxt, { ...options }) if (verbose) logFn.call(this, assetTxt, result, path, 'xml') - resolve(route.set(path, result)) + route.set(path, result) } catch (err) { reject(new Error(`Path: ${path}\n${err}`)) } } - resolve(assetTxt) + resolve() }) }) })) diff --git a/lib/zlib.js b/lib/zlib.js index 0b26a29..cf1c5c2 100644 --- a/lib/zlib.js +++ b/lib/zlib.js @@ -17,7 +17,7 @@ function gzipFn() { if (typeof level !== 'number') level = zlib.constants.Z_BEST_COMPRESSION return Promise.all((match(routeList, include, globOptions)).map(path => { - return new Promise((/** @type {(value: string) => void} */ resolve, reject) => { + return new Promise((/** @type {(value: void) => void} */ resolve, reject) => { const assetPath = route.get(path) let assetTxt = '' assetPath.on('data', chunk => (assetTxt += chunk)) @@ -27,11 +27,12 @@ function gzipFn() { const result = await gzip(assetTxt, { level }) if (verbose) logFn.call(this, assetTxt, result, path, 'gzip') resolve(route.set(path + '.gz', result)) + return } catch (err) { reject(new Error(`Path: ${path}\n${err}`)) } } - resolve(assetTxt) + resolve() }) }) })) @@ -48,7 +49,7 @@ function brotliFn() { if (typeof level !== 'number') level = zlib.constants.BROTLI_MAX_QUALITY return Promise.all((match(routeList, include, globOptions)).map(path => { - return new Promise((/** @type {(value: string) => void} */ resolve, reject) => { + return new Promise((/** @type {(value: void) => void} */ resolve, reject) => { const assetPath = route.get(path) let assetTxt = '' assetPath.on('data', chunk => (assetTxt += chunk)) @@ -57,12 +58,12 @@ function brotliFn() { try { const result = await br(assetTxt, { params: { [zlib.constants.BROTLI_PARAM_QUALITY]: level } }) if (verbose) logFn.call(this, assetTxt, result, path, 'brotli') - resolve(route.set(path + '.br', result)) + route.set(path + '.br', result) } catch (err) { reject(new Error(`Path: ${path}\n${err}`)) } } - resolve(assetTxt) + resolve() }) }) })) diff --git a/lib/zstd.js b/lib/zstd.js index 435381b..3b95bfd 100644 --- a/lib/zstd.js +++ b/lib/zstd.js @@ -14,7 +14,7 @@ function zstdFn() { if (typeof level !== 'number') level = undefined return Promise.all((match(routeList, include, globOptions)).map(path => { - return new Promise((/** @type {(value: string) => void} */ resolve, reject) => { + return new Promise((/** @type {(value: void) => void} */ resolve, reject) => { const assetPath = route.get(path) let assetTxt = '' assetPath.on('data', chunk => (assetTxt += chunk)) @@ -24,12 +24,12 @@ function zstdFn() { const input = Buffer.from(assetTxt, 'utf-8') const result = await zstd(input, level) if (verbose) logFn.call(this, assetTxt, result, path, 'zstd') - resolve(route.set(path + '.zst', result)) + route.set(path + '.zst', result) } catch (err) { reject(new Error(`Path: ${path}\n${err}`)) } } - resolve(assetTxt) + resolve() }) }) })) From 2528fcc39413b7c367446bfc49149a497e82df97 Mon Sep 17 00:00:00 2001 From: wherewhere Date: Wed, 4 Dec 2024 16:41:23 +0800 Subject: [PATCH 05/11] feat: add source map supports --- README.md | 4 ++++ index.js | 17 +++++++++++++++-- lib/css.js | 47 +++++++++++++++++++++++++++++++++++++++++++++-- lib/js.js | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++-- lib/zlib.js | 3 ++- 5 files changed, 116 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 9343c10..402084a 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,8 @@ minify: - **verbose** - Verbose output. Defaults to `false`. - **exclude** - Exclude files. Support [wildcard](http://www.globtester.com/) pattern(s) in a string or array. - **level** - Optimization level. Defaults to `2`. +- **sourceMap** - Source map options. Defaults to `false`. +- **mapIncludeSources** - Include sources in map, Defaults to `false`. - **globOptions** - See [globbing](#globbing) section. For more options, see [clean-css](https://github.com/jakubpawlowicz/clean-css). @@ -112,6 +114,8 @@ minify: - **mangle** - Mangle variable names. Defaults to `true`. Pass an object to specify [mangle options](https://github.com/terser-js/terser#mangle-options). - **output** - Output options. - To retain comments, `output: {comments: true}`. +- **sourceMap** - Source map options. Defaults to `false`. + - To include sources in map, `sourceMap: { includeSources: true }` - **globOptions** - See [globbing](#globbing) section. For more options, see [Terser](https://github.com/terser-js/terser). diff --git a/index.js b/index.js index c9235be..a646f36 100644 --- a/index.js +++ b/index.js @@ -32,6 +32,8 @@ hexo.config.minify.css = { verbose: false, exclude: ['*.min.css'], level: 2, + sourceMap: false, + mapIncludeSources: false, globOptions: { basename: true }, ...hexo.config.minify.css } @@ -44,6 +46,7 @@ hexo.config.minify.js = { compress: {}, mangle: true, output: {}, + sourceMap: false, globOptions: { basename: true }, ...hexo.config.minify.js } @@ -109,10 +112,20 @@ if (hexo.config.minify.enable === true && !(hexo.config.minify.previewServer === hexo.extend.filter.register('after_render:html', require('./lib/html').minifyHtml, hexo.config.minify.html.priority) } if (hexo.config.minify.css.enable === true) { - hexo.extend.filter.register('after_render:css', require('./lib/css').minifyCss, hexo.config.minify.css.priority) + if (hexo.config.minify.css.sourceMap) { + hexo.extend.filter.register('after_generate', require('./lib/css').minifyCssWithMap, hexo.config.minify.js.priority) + } + else { + hexo.extend.filter.register('after_render:css', require('./lib/css').minifyCss, hexo.config.minify.css.priority) + } } if (hexo.config.minify.js.enable === true) { - hexo.extend.filter.register('after_render:js', require('./lib/js').minifyJs, hexo.config.minify.js.priority) + if (hexo.config.minify.js.sourceMap) { + hexo.extend.filter.register('after_generate', require('./lib/js').minifyJsWithMap, hexo.config.minify.js.priority) + } + else { + hexo.extend.filter.register('after_render:js', require('./lib/js').minifyJs, hexo.config.minify.js.priority) + } } if (hexo.config.minify.svg.enable === true) { hexo.extend.filter.register('after_generate', require('./lib/svg').minifySvg, hexo.config.minify.svg.priority) diff --git a/lib/css.js b/lib/css.js index 5fe78e7..d7fe897 100644 --- a/lib/css.js +++ b/lib/css.js @@ -1,6 +1,6 @@ 'use strict' const CleanCSS = require('clean-css') -const { isMatch, logFn } = require('./tools') +const { isMatch, match, logFn } = require('./tools') /** * @param {string} str @@ -13,6 +13,7 @@ async function minifyCss(str, data) { const path = data.path const { exclude, globOptions, verbose } = options + options.returnPromise = true if (isMatch(path, exclude, globOptions)) return str @@ -25,6 +26,48 @@ async function minifyCss(str, data) { } } +function minifyCssWithMap() { + const hexo = this + const options = hexo.config.minify.css + const { parse } = require('path') + + const route = hexo.route + const routeList = route.list() + /** @type {{ exclude: string[] }} */ + const { exclude, globOptions, verbose } = options + const include = ['*.css', ...exclude.map(x => `!${x}`)] + options.returnPromise = true + const cleanCSS = new CleanCSS(options) + + return Promise.all((match(routeList, include, globOptions)).map(path => { + return new Promise((/** @type {(value: void) => void} */ resolve, reject) => { + const assetPath = route.get(path) + let assetTxt = '' + assetPath.on('data', chunk => (assetTxt += chunk)) + assetPath.on('end', async () => { + if (assetTxt.length) { + try { + const { base, ext, name } = parse(path) + const { styles, sourceMap } = await cleanCSS.minify(assetTxt) + if (verbose) logFn.call(this, assetTxt, result, path, 'css') + route.set(path, `${styles}\n/*# sourceMappingURL=${base}.map */`) + const map = sourceMap.toJSON() + map.sources = [`${name}.source${ext}`] + if (options.mapIncludeSources === true) { + map.sourcesContent = [assetTxt] + } + route.set(`${path}.map`, JSON.stringify(map)) + } catch (err) { + reject(new Error(`Path: ${path}\n${err}`)) + } + } + resolve() + }) + }) + })) +} + module.exports = { - minifyCss + minifyCss, + minifyCssWithMap } \ No newline at end of file diff --git a/lib/js.js b/lib/js.js index f32378c..50f5955 100644 --- a/lib/js.js +++ b/lib/js.js @@ -1,6 +1,6 @@ 'use strict' const { minify: terserMinify } = require('terser') -const { isMatch, logFn } = require('./tools') +const { isMatch, match, logFn } = require('./tools') /** * @param {string} str @@ -31,6 +31,54 @@ async function minifyJs(str, data) { } } +function minifyJsWithMap() { + const hexo = this + const options = hexo.config.minify.js + const { parse } = require('path') + + const route = hexo.route + const routeList = route.list() + /** @type {{ exclude: string[] }} */ + const { exclude, globOptions, verbose, ...jsOptions } = options + const include = ['*.js', ...exclude.map(x => `!${x}`)] + + // Terser doesn't like unsupported options + delete jsOptions.enable + delete jsOptions.priority + // Old option, retained to avoid crash when upgrading to v4 + delete jsOptions.logger + + return Promise.all((match(routeList, include, globOptions)).map(path => { + return new Promise((/** @type {(value: void) => void} */ resolve, reject) => { + const assetPath = route.get(path) + let assetTxt = '' + assetPath.on('data', chunk => (assetTxt += chunk)) + assetPath.on('end', async () => { + if (assetTxt.length) { + try { + const { base, ext, name } = parse(path) + jsOptions.sourceMap = { + ...jsOptions.sourceMap, + filename: base, + asObject: true, + url: `${base}.map` + } + const { code, map } = await terserMinify(assetTxt, { ...jsOptions }) + if (verbose) logFn.call(this, assetTxt, result, path, 'js') + route.set(path, code) + map.sources = [`${name}.source${ext}`] + route.set(`${path}.map`, JSON.stringify(map)) + } catch (err) { + reject(new Error(`Path: ${path}\n${err}`)) + } + } + resolve() + }) + }) + })) +} + module.exports = { - minifyJs + minifyJs, + minifyJsWithMap } \ No newline at end of file diff --git a/lib/zlib.js b/lib/zlib.js index cf1c5c2..5b3a8f5 100644 --- a/lib/zlib.js +++ b/lib/zlib.js @@ -10,7 +10,7 @@ function gzipFn() { const options = hexo.config.minify.gzip const route = hexo.route - /** @type {string} */ + /** @type {string[]} */ const routeList = route.list() const { globOptions, include, verbose } = options let level = options.level @@ -43,6 +43,7 @@ function brotliFn() { const options = hexo.config.minify.brotli const route = hexo.route + /** @type {string[]} */ const routeList = route.list() const { globOptions, include, verbose } = options let level = options.level From 0bf55ab47458ef848b04cff506ca9beca85cd1f1 Mon Sep 17 00:00:00 2001 From: wherewhere Date: Fri, 6 Dec 2024 15:49:02 +0800 Subject: [PATCH 06/11] feat: add option to check compress size --- README.md | 3 +++ index.js | 3 +++ lib/tools.js | 6 +++--- lib/zlib.js | 19 ++++++++++++------- lib/zstd.js | 8 +++++--- 5 files changed, 26 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 402084a..7a44dc1 100644 --- a/README.md +++ b/README.md @@ -222,6 +222,7 @@ minify: - **include** - Include files. Support [wildcard](http://www.globtester.com/) pattern(s) in a string or array. - Support one-liner, `include: ['*.html','*.css','*.js']`. - Must include asterisk and single quotes. `.html` is invalid. `'*.html'` is valid. +- **ensureCompressed** - Ensure the compressed file is smaller than the original, otherwise do not output. Defaults to `true`. - **globOptions** - See [globbing](#globbing) section. - **level** - Compression level; lower value may results in faster compression but slightly larger (compressed) file. Range `1-9`. Defaults to `9`, or the value of [`zlib.constants.Z_BEST_COMPRESSION`](https://nodejs.org/docs/latest-v12.x/api/zlib.html#zlib_zlib_constants) @@ -252,6 +253,7 @@ minify: - **priority** - Plugin's priority. Defaults to `10`. - **verbose** - Verbose output. Defaults to `false`. - **include** - Include files. Support [wildcard](http://www.globtester.com/) pattern(s) in a string or array. +- **ensureCompressed** - Ensure the compressed file is smaller than the original, otherwise do not output. Defaults to `true`. - **globOptions** - See [globbing](#globbing) section. - **level** - Compression level. Range `1-11`. Defaults to `11`, or the value of [`zlib.constants.BROTLI_MAX_QUALITY`](https://nodejs.org/docs/latest-v12.x/api/zlib.html#zlib_brotli_constants) @@ -282,6 +284,7 @@ minify: - **priority** - Plugin's priority. Defaults to `10`. - **verbose** - Verbose output. Defaults to `false`. - **include** - Include files. Support [wildcard](http://www.globtester.com/) pattern(s) in a string or array. +- **ensureCompressed** - Ensure the compressed file is smaller than the original, otherwise do not output. Defaults to `true`. - **globOptions** - See [globbing](#globbing) section. - **level** - Compression level. Range `1-22`. Defaults to `3`, or the value of [`DEFAULT_LEVEL`](https://github.com/mongodb-js/zstd/blob/a3a08c61c9045411c8275e248498dbc583457fb5/src/lib.rs#L9) diff --git a/index.js b/index.js index a646f36..85d4033 100644 --- a/index.js +++ b/index.js @@ -85,6 +85,7 @@ hexo.config.minify.gzip = { priority: 10, verbose: false, include: ['*.html', '*.css', '*.js', '*.map', '*.wasm', '*.txt', '*.ttf', '*.atom', '*.stl', '*.xml', '*.svg', '*.eot', '*.json', '*.webmanifest'], + ensureCompressed: true, globOptions: { basename: true }, ...hexo.config.minify.gzip } @@ -94,6 +95,7 @@ hexo.config.minify.brotli = { priority: 10, verbose: false, include: ['*.html', '*.css', '*.js', '*.map', '*.wasm', '*.txt', '*.ttf', '*.atom', '*.stl', '*.xml', '*.svg', '*.eot', '*.json', '*.webmanifest'], + ensureCompressed: true, globOptions: { basename: true }, ...hexo.config.minify.brotli } @@ -103,6 +105,7 @@ hexo.config.minify.zstd = { priority: 10, verbose: false, include: ['*.html', '*.css', '*.js', '*.map', '*.wasm', '*.txt', '*.ttf', '*.atom', '*.stl', '*.xml', '*.svg', '*.eot', '*.json', '*.webmanifest'], + ensureCompressed: true, globOptions: { basename: true }, ...hexo.config.minify.zstd } diff --git a/lib/tools.js b/lib/tools.js index 2268fd1..d381d4c 100644 --- a/lib/tools.js +++ b/lib/tools.js @@ -54,10 +54,10 @@ function match(paths = [], patterns = [], options = {}) { } /** - * @param {string} original - * @param {string} ext - * @param {string} minified + * @param {{ length: number }} original + * @param {{ length: number }} minified * @param {string} path + * @param {string} ext */ function logFn(original, minified, path, ext) { const saved = ((original.length - minified.length) / original.length * 100).toFixed(2) diff --git a/lib/zlib.js b/lib/zlib.js index 5b3a8f5..e3de80b 100644 --- a/lib/zlib.js +++ b/lib/zlib.js @@ -12,7 +12,7 @@ function gzipFn() { const route = hexo.route /** @type {string[]} */ const routeList = route.list() - const { globOptions, include, verbose } = options + const { globOptions, include, verbose, ensureCompressed } = options let level = options.level if (typeof level !== 'number') level = zlib.constants.Z_BEST_COMPRESSION @@ -25,9 +25,11 @@ function gzipFn() { if (assetTxt.length) { try { const result = await gzip(assetTxt, { level }) - if (verbose) logFn.call(this, assetTxt, result, path, 'gzip') - resolve(route.set(path + '.gz', result)) - return + const buffer = Buffer.from(assetTxt) + if (verbose) logFn.call(this, buffer, result, path, 'gzip') + if (!ensureCompressed || buffer.length > result.length) { + route.set(path + '.gz', result) + } } catch (err) { reject(new Error(`Path: ${path}\n${err}`)) } @@ -45,7 +47,7 @@ function brotliFn() { const route = hexo.route /** @type {string[]} */ const routeList = route.list() - const { globOptions, include, verbose } = options + const { globOptions, include, verbose, ensureCompressed } = options let level = options.level if (typeof level !== 'number') level = zlib.constants.BROTLI_MAX_QUALITY @@ -58,8 +60,11 @@ function brotliFn() { if (assetTxt.length) { try { const result = await br(assetTxt, { params: { [zlib.constants.BROTLI_PARAM_QUALITY]: level } }) - if (verbose) logFn.call(this, assetTxt, result, path, 'brotli') - route.set(path + '.br', result) + const buffer = Buffer.from(assetTxt) + if (verbose) logFn.call(this, buffer, result, path, 'brotli') + if (!ensureCompressed || buffer.length > result.length) { + route.set(path + '.br', result) + } } catch (err) { reject(new Error(`Path: ${path}\n${err}`)) } diff --git a/lib/zstd.js b/lib/zstd.js index 3b95bfd..a3b6a75 100644 --- a/lib/zstd.js +++ b/lib/zstd.js @@ -9,7 +9,7 @@ function zstdFn() { const route = hexo.route /** @type {string[]} */ const routeList = route.list() - const { globOptions, include, verbose } = options + const { globOptions, include, verbose, ensureCompressed } = options let level = options.level if (typeof level !== 'number') level = undefined @@ -23,8 +23,10 @@ function zstdFn() { try { const input = Buffer.from(assetTxt, 'utf-8') const result = await zstd(input, level) - if (verbose) logFn.call(this, assetTxt, result, path, 'zstd') - route.set(path + '.zst', result) + if (verbose) logFn.call(this, input, result, path, 'zstd') + if (!ensureCompressed || input.length > result.length) { + route.set(path + '.zst', result) + } } catch (err) { reject(new Error(`Path: ${path}\n${err}`)) } From c2ec42268058723a7804facc5fe37e44873dff57 Mon Sep 17 00:00:00 2001 From: wherewhere Date: Sat, 14 Dec 2024 15:47:25 +0800 Subject: [PATCH 07/11] fix: fixed css minify --- lib/css.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/css.js b/lib/css.js index d7fe897..e0a36fe 100644 --- a/lib/css.js +++ b/lib/css.js @@ -13,11 +13,11 @@ async function minifyCss(str, data) { const path = data.path const { exclude, globOptions, verbose } = options - options.returnPromise = true if (isMatch(path, exclude, globOptions)) return str try { + console.log(path) const styles = await new CleanCSS(options).minify(str).styles if (verbose) logFn.call(this, str, styles, path, 'css') return styles @@ -36,7 +36,6 @@ function minifyCssWithMap() { /** @type {{ exclude: string[] }} */ const { exclude, globOptions, verbose } = options const include = ['*.css', ...exclude.map(x => `!${x}`)] - options.returnPromise = true const cleanCSS = new CleanCSS(options) return Promise.all((match(routeList, include, globOptions)).map(path => { From 607a226ce023760326dec4240710957ad42dfe76 Mon Sep 17 00:00:00 2001 From: wherewhere Date: Sun, 15 Dec 2024 14:40:52 +0800 Subject: [PATCH 08/11] feat: upgrade zstd and move it to optionalDependencies --- lib/css.js | 1 - package.json | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/css.js b/lib/css.js index e0a36fe..269e826 100644 --- a/lib/css.js +++ b/lib/css.js @@ -17,7 +17,6 @@ async function minifyCss(str, data) { if (isMatch(path, exclude, globOptions)) return str try { - console.log(path) const styles = await new CleanCSS(options).minify(str).styles if (verbose) logFn.call(this, str, styles, path, 'css') return styles diff --git a/package.json b/package.json index c1cebe9..13656ed 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,6 @@ "url": "git+https://github.com/curbengh/hexo-yam.git" }, "dependencies": { - "@mongodb-js/zstd": "^1.2.0", "clean-css": "^5.1.2", "html-minifier-terser": "^7.2.0", "micromatch": "^4.0.2", @@ -39,6 +38,9 @@ "jest": "^29.1.2", "standard": "^17.0.0" }, + "optionalDependencies": { + "@mongodb-js/zstd": "^2.0.0" + }, "keywords": [ "minify", "compress", From bda758177aec6b48eb8a8d723d38a3ae71a27ed5 Mon Sep 17 00:00:00 2001 From: wherewhere Date: Sun, 15 Dec 2024 16:25:33 +0800 Subject: [PATCH 09/11] feat: add @bokuweb/zstd-wasm as option --- .github/workflows/tester.yml | 14 +------------- lib/zstd.js | 33 ++++++++++++++++++++++++++++++--- package.json | 13 +++++++++++-- 3 files changed, 42 insertions(+), 18 deletions(-) diff --git a/.github/workflows/tester.yml b/.github/workflows/tester.yml index 72243f9..7ae0ec1 100644 --- a/.github/workflows/tester.yml +++ b/.github/workflows/tester.yml @@ -24,20 +24,8 @@ jobs: restore-keys: ${{ runner.os }}-npm-cache - name: Install Dependencies run: npm install - - name: Determine zstd binary version - shell: bash - run: | - case "$RUNNER_OS" in - "Linux") - echo "PLATFORM=linux-x64-gnu" >> "$GITHUB_ENV" ;; - "Windows") - echo "PLATFORM=win32-x64-msvc" >> "$GITHUB_ENV" ;; - "macOS") - echo "PLATFORM=darwin-arm64" >> "$GITHUB_ENV" ;; - esac - name: Install zstd binary - shell: bash - run: npm install "@mongodb-js/zstd-$PLATFORM" + run: npm install "@mongodb-js/zstd" - name: Test run: npm run test env: diff --git a/lib/zstd.js b/lib/zstd.js index a3b6a75..a0ecf36 100644 --- a/lib/zstd.js +++ b/lib/zstd.js @@ -1,8 +1,31 @@ 'use strict' -const { compress: zstd } = require('@mongodb-js/zstd') + +/** + * @returns {{ compress: (data: Buffer, level?: number) => Promise, init: () => Promise }} + */ +function importZstd() { + try { + const { compress } = require('@mongodb-js/zstd') + return { compress }; + } + catch (ex) { + try { + const { init, compress } = require('@bokuweb/zstd-wasm') + return { + compress: async (buf, level) => Buffer.from(compress(buf, level)), + init + } + } + catch { + throw ex; + } + } +} + +const { compress: zstd, init = undefined } = importZstd() const { match, logFn } = require('./tools') -function zstdFn() { +async function zstdFn() { const hexo = this const options = hexo.config.minify.zstd @@ -13,7 +36,11 @@ function zstdFn() { let level = options.level if (typeof level !== 'number') level = undefined - return Promise.all((match(routeList, include, globOptions)).map(path => { + if (typeof init === 'function') { + await init(); + } + + await Promise.all((match(routeList, include, globOptions)).map(path => { return new Promise((/** @type {(value: void) => void} */ resolve, reject) => { const assetPath = route.get(path) let assetTxt = '' diff --git a/package.json b/package.json index 13656ed..33d895c 100644 --- a/package.json +++ b/package.json @@ -38,8 +38,17 @@ "jest": "^29.1.2", "standard": "^17.0.0" }, - "optionalDependencies": { - "@mongodb-js/zstd": "^2.0.0" + "peerDependencies": { + "@mongodb-js/zstd": "^2.0.0", + "@bokuweb/zstd-wasm": "^0.0.22" + }, + "peerDependenciesMeta": { + "@mongodb-js/zstd": { + "optional": true + }, + "@bokuweb/zstd-wasm": { + "optional": true + } }, "keywords": [ "minify", From a36d5ef2df990f5e288291597d50eca4c11e4b84 Mon Sep 17 00:00:00 2001 From: wherewhere Date: Wed, 18 Dec 2024 13:21:37 +0800 Subject: [PATCH 10/11] feat: upgrade minify xml to v4 --- lib/xml.js | 7 ++++--- package.json | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/xml.js b/lib/xml.js index 264345d..9d5799f 100644 --- a/lib/xml.js +++ b/lib/xml.js @@ -1,8 +1,9 @@ 'use strict' -const { minify: compressXml } = require('minify-xml') const { match, logFn } = require('./tools') -function minifyXml() { +async function minifyXml() { + const { minify: compressXml } = await import('minify-xml') + const hexo = this const options = hexo.config.minify.xml @@ -11,7 +12,7 @@ function minifyXml() { const routeList = route.list() const { globOptions, include, verbose } = options - return Promise.all((match(routeList, include, globOptions)).map(path => { + await Promise.all((match(routeList, include, globOptions)).map(path => { return new Promise((/** @type {(value: void) => void} */ resolve, reject) => { const assetPath = route.get(path) let assetTxt = '' diff --git a/package.json b/package.json index 33d895c..82a2b89 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "clean-css": "^5.1.2", "html-minifier-terser": "^7.2.0", "micromatch": "^4.0.2", - "minify-xml": "^3.2.0", + "minify-xml": "^4.5.2", "svgo": "^3.0.0", "terser": "^5.3.0" }, From 9fa79f78d156ecccad5558b06e71b07017140d60 Mon Sep 17 00:00:00 2001 From: wherewhere Date: Tue, 14 Jan 2025 16:59:59 +0800 Subject: [PATCH 11/11] feat: add hexo type define --- index.js | 2 ++ lib/css.js | 4 ++++ lib/html.js | 1 + lib/js.js | 4 ++++ lib/json.js | 3 +++ lib/svg.js | 3 +++ lib/xml.js | 3 +++ lib/zlib.js | 6 ++++++ lib/zstd.js | 3 +++ 9 files changed, 29 insertions(+) diff --git a/index.js b/index.js index 85d4033..15ab3e6 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,8 @@ /* global hexo */ 'use strict' +/** @typedef {import("@types/hexo")} */ + hexo.config.minify = { enable: true, previewServer: true, diff --git a/lib/css.js b/lib/css.js index 269e826..e7a8e58 100644 --- a/lib/css.js +++ b/lib/css.js @@ -5,6 +5,7 @@ const { isMatch, match, logFn } = require('./tools') /** * @param {string} str * @param {{ path: string }} data + * @this {import('@types/hexo')} */ async function minifyCss(str, data) { const hexo = this @@ -25,6 +26,9 @@ async function minifyCss(str, data) { } } +/** + * @this {import('@types/hexo')} + */ function minifyCssWithMap() { const hexo = this const options = hexo.config.minify.css diff --git a/lib/html.js b/lib/html.js index bfcb758..863a3b0 100644 --- a/lib/html.js +++ b/lib/html.js @@ -5,6 +5,7 @@ const { isMatch, logFn } = require('./tools') /** * @param {string} str * @param {{ path: string }} data + * @this {import('@types/hexo')} */ async function minifyHtml(str, data) { const hexo = this diff --git a/lib/js.js b/lib/js.js index 50f5955..251bb2d 100644 --- a/lib/js.js +++ b/lib/js.js @@ -5,6 +5,7 @@ const { isMatch, match, logFn } = require('./tools') /** * @param {string} str * @param {{ path: string }} data + * @this {import('@types/hexo')} */ async function minifyJs(str, data) { const hexo = this @@ -31,6 +32,9 @@ async function minifyJs(str, data) { } } +/** + * @this {import('@types/hexo')} + */ function minifyJsWithMap() { const hexo = this const options = hexo.config.minify.js diff --git a/lib/json.js b/lib/json.js index 287ceaf..9f40e51 100644 --- a/lib/json.js +++ b/lib/json.js @@ -1,6 +1,9 @@ 'use strict' const { match, logFn } = require('./tools') +/** + * @this {import('@types/hexo')} + */ function minifyJson() { const hexo = this const options = hexo.config.minify.json diff --git a/lib/svg.js b/lib/svg.js index f8d478a..0cfb0e0 100644 --- a/lib/svg.js +++ b/lib/svg.js @@ -2,6 +2,9 @@ const { optimize: svgOptimize } = require('svgo') const { match, logFn } = require('./tools') +/** + * @this {import('@types/hexo')} + */ function minifySvg() { const hexo = this const options = hexo.config.minify.svg diff --git a/lib/xml.js b/lib/xml.js index 9d5799f..d13061c 100644 --- a/lib/xml.js +++ b/lib/xml.js @@ -1,6 +1,9 @@ 'use strict' const { match, logFn } = require('./tools') +/** + * @this {import('@types/hexo')} + */ async function minifyXml() { const { minify: compressXml } = await import('minify-xml') diff --git a/lib/zlib.js b/lib/zlib.js index e3de80b..7b0a0c2 100644 --- a/lib/zlib.js +++ b/lib/zlib.js @@ -5,6 +5,9 @@ const gzip = promisify(zlib.gzip) const br = promisify(zlib.brotliCompress) const { match, logFn } = require('./tools') +/** + * @this {import('@types/hexo')} + */ function gzipFn() { const hexo = this const options = hexo.config.minify.gzip @@ -40,6 +43,9 @@ function gzipFn() { })) } +/** + * @this {import('@types/hexo')} + */ function brotliFn() { const hexo = this const options = hexo.config.minify.brotli diff --git a/lib/zstd.js b/lib/zstd.js index a0ecf36..1b6701c 100644 --- a/lib/zstd.js +++ b/lib/zstd.js @@ -25,6 +25,9 @@ function importZstd() { const { compress: zstd, init = undefined } = importZstd() const { match, logFn } = require('./tools') +/** + * @this {import('@types/hexo')} + */ async function zstdFn() { const hexo = this const options = hexo.config.minify.zstd