mirror of https://github.com/curbengh/hexo-yam
Merge 9fa79f78d1
into 4bd000bfee
This commit is contained in:
commit
22bf3b409e
|
@ -24,20 +24,8 @@ jobs:
|
||||||
restore-keys: ${{ runner.os }}-npm-cache
|
restore-keys: ${{ runner.os }}-npm-cache
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: npm install
|
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
|
- name: Install zstd binary
|
||||||
shell: bash
|
run: npm install "@mongodb-js/zstd"
|
||||||
run: npm install "@mongodb-js/zstd-$PLATFORM"
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: npm run test
|
run: npm run test
|
||||||
env:
|
env:
|
||||||
|
|
|
@ -5,3 +5,4 @@ tmp/
|
||||||
*.log
|
*.log
|
||||||
coverage/
|
coverage/
|
||||||
bun.lockb
|
bun.lockb
|
||||||
|
*.bak
|
||||||
|
|
28
README.md
28
README.md
|
@ -39,10 +39,11 @@ minify:
|
||||||
css:
|
css:
|
||||||
js:
|
js:
|
||||||
svg:
|
svg:
|
||||||
gzip:
|
|
||||||
brotli:
|
|
||||||
xml:
|
xml:
|
||||||
json:
|
json:
|
||||||
|
gzip:
|
||||||
|
brotli:
|
||||||
|
zstd:
|
||||||
```
|
```
|
||||||
|
|
||||||
- **enable** - Enable the plugin. Defaults to `true`.
|
- **enable** - Enable the plugin. Defaults to `true`.
|
||||||
|
@ -51,10 +52,11 @@ minify:
|
||||||
- **css** - See [CSS](#css) section
|
- **css** - See [CSS](#css) section
|
||||||
- **js** - See [JS](#js) section
|
- **js** - See [JS](#js) section
|
||||||
- **svg** - See [SVG](#svg) section
|
- **svg** - See [SVG](#svg) section
|
||||||
- **gzip** - See [Gzip](#gzip) section
|
|
||||||
- **brotli** - See [Brotli](#brotli) section
|
|
||||||
- **xml** - See [XML](#xml) section
|
- **xml** - See [XML](#xml) section
|
||||||
- **json** - See [JSON](#json) section
|
- **json** - See [JSON](#json) section
|
||||||
|
- **gzip** - See [Gzip](#gzip) section
|
||||||
|
- **brotli** - See [Brotli](#brotli) section
|
||||||
|
- **zstd** - See [Zstd](#zstd) section
|
||||||
|
|
||||||
## HTML
|
## HTML
|
||||||
|
|
||||||
|
@ -88,6 +90,8 @@ minify:
|
||||||
- **verbose** - Verbose output. Defaults to `false`.
|
- **verbose** - Verbose output. Defaults to `false`.
|
||||||
- **exclude** - Exclude files. Support [wildcard](http://www.globtester.com/) pattern(s) in a string or array.
|
- **exclude** - Exclude files. Support [wildcard](http://www.globtester.com/) pattern(s) in a string or array.
|
||||||
- **level** - Optimization level. Defaults to `2`.
|
- **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.
|
- **globOptions** - See [globbing](#globbing) section.
|
||||||
|
|
||||||
For more options, see [clean-css](https://github.com/jakubpawlowicz/clean-css).
|
For more options, see [clean-css](https://github.com/jakubpawlowicz/clean-css).
|
||||||
|
@ -110,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).
|
- **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.
|
- **output** - Output options.
|
||||||
- To retain comments, `output: {comments: true}`.
|
- 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.
|
- **globOptions** - See [globbing](#globbing) section.
|
||||||
|
|
||||||
For more options, see [Terser](https://github.com/terser-js/terser).
|
For more options, see [Terser](https://github.com/terser-js/terser).
|
||||||
|
@ -175,7 +181,9 @@ minify:
|
||||||
enable: false
|
enable: false
|
||||||
include:
|
include:
|
||||||
- "*.json"
|
- "*.json"
|
||||||
|
- "*.webmanifest"
|
||||||
- "!*.min.json"
|
- "!*.min.json"
|
||||||
|
- "!*.min.webmanifest"
|
||||||
```
|
```
|
||||||
|
|
||||||
- **enable** - Enable the plugin. Defaults to `false`.
|
- **enable** - Enable the plugin. Defaults to `false`.
|
||||||
|
@ -195,6 +203,8 @@ minify:
|
||||||
- "*.html"
|
- "*.html"
|
||||||
- "*.css"
|
- "*.css"
|
||||||
- "*.js"
|
- "*.js"
|
||||||
|
- "*.map"
|
||||||
|
- "*.wasm"
|
||||||
- "*.txt"
|
- "*.txt"
|
||||||
- "*.ttf"
|
- "*.ttf"
|
||||||
- "*.atom"
|
- "*.atom"
|
||||||
|
@ -203,6 +213,7 @@ minify:
|
||||||
- "*.svg"
|
- "*.svg"
|
||||||
- "*.eot"
|
- "*.eot"
|
||||||
- "*.json"
|
- "*.json"
|
||||||
|
- "*.webmanifest"
|
||||||
```
|
```
|
||||||
|
|
||||||
- **enable** - Enable the plugin. Defaults to `true`.
|
- **enable** - Enable the plugin. Defaults to `true`.
|
||||||
|
@ -211,6 +222,7 @@ minify:
|
||||||
- **include** - Include files. Support [wildcard](http://www.globtester.com/) pattern(s) in a string or array.
|
- **include** - Include files. Support [wildcard](http://www.globtester.com/) pattern(s) in a string or array.
|
||||||
- Support one-liner, `include: ['*.html','*.css','*.js']`.
|
- Support one-liner, `include: ['*.html','*.css','*.js']`.
|
||||||
- Must include asterisk and single quotes. `.html` is invalid. `'*.html'` is valid.
|
- 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.
|
- **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)
|
- **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)
|
||||||
|
|
||||||
|
@ -224,6 +236,8 @@ minify:
|
||||||
- "*.html"
|
- "*.html"
|
||||||
- "*.css"
|
- "*.css"
|
||||||
- "*.js"
|
- "*.js"
|
||||||
|
- "*.map"
|
||||||
|
- "*.wasm"
|
||||||
- "*.txt"
|
- "*.txt"
|
||||||
- "*.ttf"
|
- "*.ttf"
|
||||||
- "*.atom"
|
- "*.atom"
|
||||||
|
@ -232,12 +246,14 @@ minify:
|
||||||
- "*.svg"
|
- "*.svg"
|
||||||
- "*.eot"
|
- "*.eot"
|
||||||
- "*.json"
|
- "*.json"
|
||||||
|
- "*.webmanifest"
|
||||||
```
|
```
|
||||||
|
|
||||||
- **enable** - Enable the plugin. Defaults to `true`.
|
- **enable** - Enable the plugin. Defaults to `true`.
|
||||||
- **priority** - Plugin's priority. Defaults to `10`.
|
- **priority** - Plugin's priority. Defaults to `10`.
|
||||||
- **verbose** - Verbose output. Defaults to `false`.
|
- **verbose** - Verbose output. Defaults to `false`.
|
||||||
- **include** - Include files. Support [wildcard](http://www.globtester.com/) pattern(s) in a string or array.
|
- **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.
|
- **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)
|
- **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)
|
||||||
|
|
||||||
|
@ -251,6 +267,8 @@ minify:
|
||||||
- "*.html"
|
- "*.html"
|
||||||
- "*.css"
|
- "*.css"
|
||||||
- "*.js"
|
- "*.js"
|
||||||
|
- "*.map"
|
||||||
|
- "*.wasm"
|
||||||
- "*.txt"
|
- "*.txt"
|
||||||
- "*.ttf"
|
- "*.ttf"
|
||||||
- "*.atom"
|
- "*.atom"
|
||||||
|
@ -259,12 +277,14 @@ minify:
|
||||||
- "*.svg"
|
- "*.svg"
|
||||||
- "*.eot"
|
- "*.eot"
|
||||||
- "*.json"
|
- "*.json"
|
||||||
|
- "*.webmanifest"
|
||||||
```
|
```
|
||||||
|
|
||||||
- **enable** - Enable the plugin. Defaults to `false`.
|
- **enable** - Enable the plugin. Defaults to `false`.
|
||||||
- **priority** - Plugin's priority. Defaults to `10`.
|
- **priority** - Plugin's priority. Defaults to `10`.
|
||||||
- **verbose** - Verbose output. Defaults to `false`.
|
- **verbose** - Verbose output. Defaults to `false`.
|
||||||
- **include** - Include files. Support [wildcard](http://www.globtester.com/) pattern(s) in a string or array.
|
- **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.
|
- **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)
|
- **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)
|
||||||
|
|
||||||
|
|
169
index.js
169
index.js
|
@ -1,12 +1,15 @@
|
||||||
/* global hexo */
|
/* global hexo */
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
hexo.config.minify = Object.assign({
|
/** @typedef {import("@types/hexo")} */
|
||||||
enable: true,
|
|
||||||
previewServer: true
|
|
||||||
}, hexo.config.minify)
|
|
||||||
|
|
||||||
hexo.config.minify.html = Object.assign({
|
hexo.config.minify = {
|
||||||
|
enable: true,
|
||||||
|
previewServer: true,
|
||||||
|
...hexo.config.minify
|
||||||
|
}
|
||||||
|
|
||||||
|
hexo.config.minify.html = {
|
||||||
enable: true,
|
enable: true,
|
||||||
priority: 10,
|
priority: 10,
|
||||||
verbose: false,
|
verbose: false,
|
||||||
|
@ -21,19 +24,23 @@ hexo.config.minify.html = Object.assign({
|
||||||
removeStyleLinkTypeAttributes: true,
|
removeStyleLinkTypeAttributes: true,
|
||||||
minifyJS: true,
|
minifyJS: true,
|
||||||
minifyCSS: true,
|
minifyCSS: true,
|
||||||
globOptions: { basename: true }
|
globOptions: { basename: true },
|
||||||
}, hexo.config.minify.html)
|
...hexo.config.minify.html
|
||||||
|
}
|
||||||
|
|
||||||
hexo.config.minify.css = Object.assign({
|
hexo.config.minify.css = {
|
||||||
enable: true,
|
enable: true,
|
||||||
priority: 10,
|
priority: 10,
|
||||||
verbose: false,
|
verbose: false,
|
||||||
exclude: ['*.min.css'],
|
exclude: ['*.min.css'],
|
||||||
level: 2,
|
level: 2,
|
||||||
globOptions: { basename: true }
|
sourceMap: false,
|
||||||
}, hexo.config.minify.css)
|
mapIncludeSources: false,
|
||||||
|
globOptions: { basename: true },
|
||||||
|
...hexo.config.minify.css
|
||||||
|
}
|
||||||
|
|
||||||
hexo.config.minify.js = Object.assign({
|
hexo.config.minify.js = {
|
||||||
enable: true,
|
enable: true,
|
||||||
priority: 10,
|
priority: 10,
|
||||||
verbose: false,
|
verbose: false,
|
||||||
|
@ -41,68 +48,114 @@ hexo.config.minify.js = Object.assign({
|
||||||
compress: {},
|
compress: {},
|
||||||
mangle: true,
|
mangle: true,
|
||||||
output: {},
|
output: {},
|
||||||
globOptions: { basename: true }
|
sourceMap: false,
|
||||||
}, hexo.config.minify.js)
|
globOptions: { basename: true },
|
||||||
|
...hexo.config.minify.js
|
||||||
|
}
|
||||||
|
|
||||||
hexo.config.minify.svg = Object.assign({
|
hexo.config.minify.svg = {
|
||||||
enable: true,
|
enable: true,
|
||||||
priority: 10,
|
priority: 10,
|
||||||
verbose: false,
|
verbose: false,
|
||||||
include: ['*.svg', '!*.min.svg'],
|
include: ['*.svg', '!*.min.svg'],
|
||||||
plugins: {},
|
plugins: {},
|
||||||
globOptions: { basename: true }
|
globOptions: { basename: true },
|
||||||
}, hexo.config.minify.svg)
|
...hexo.config.minify.svg
|
||||||
|
}
|
||||||
|
|
||||||
hexo.config.minify.gzip = Object.assign({
|
hexo.config.minify.xml = {
|
||||||
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 = Object.assign({
|
|
||||||
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 = Object.assign({
|
|
||||||
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 = Object.assign({
|
|
||||||
enable: false,
|
enable: false,
|
||||||
priority: 10,
|
priority: 10,
|
||||||
verbose: false,
|
verbose: false,
|
||||||
include: ['*.xml', '!*.min.xml'],
|
include: ['*.xml', '!*.min.xml'],
|
||||||
removeComments: true,
|
removeComments: true,
|
||||||
globOptions: { basename: true }
|
globOptions: { basename: true },
|
||||||
}, hexo.config.minify.xml)
|
...hexo.config.minify.xml
|
||||||
|
}
|
||||||
|
|
||||||
hexo.config.minify.json = Object.assign({
|
hexo.config.minify.json = {
|
||||||
enable: false,
|
enable: false,
|
||||||
priority: 10,
|
priority: 10,
|
||||||
verbose: false,
|
verbose: false,
|
||||||
include: ['*.json', '!*.min.json'],
|
include: ['*.json', '*.webmanifest', '!*.min.json', '!*.min.webmanifest'],
|
||||||
globOptions: { basename: true }
|
globOptions: { basename: true },
|
||||||
}, hexo.config.minify.json)
|
...hexo.config.minify.json
|
||||||
|
}
|
||||||
|
|
||||||
|
hexo.config.minify.gzip = {
|
||||||
|
enable: true,
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
hexo.config.minify.brotli = {
|
||||||
|
enable: true,
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
hexo.config.minify.zstd = {
|
||||||
|
enable: false,
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
if (hexo.config.minify.enable === true && !(hexo.config.minify.previewServer === true && ['s', 'server'].includes(hexo.env.cmd))) {
|
if (hexo.config.minify.enable === true && !(hexo.config.minify.previewServer === true && ['s', 'server'].includes(hexo.env.cmd))) {
|
||||||
const filter = require('./lib/filter')
|
if (hexo.config.minify.html.enable === true) {
|
||||||
hexo.extend.filter.register('after_render:html', filter.minifyHtml, hexo.config.minify.html.priority)
|
hexo.extend.filter.register('after_render:html', require('./lib/html').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)
|
if (hexo.config.minify.css.enable === true) {
|
||||||
hexo.extend.filter.register('after_generate', filter.minifySvg, hexo.config.minify.svg.priority)
|
if (hexo.config.minify.css.sourceMap) {
|
||||||
hexo.extend.filter.register('after_generate', filter.gzipFn, hexo.config.minify.gzip.priority)
|
hexo.extend.filter.register('after_generate', require('./lib/css').minifyCssWithMap, hexo.config.minify.js.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)
|
else {
|
||||||
hexo.extend.filter.register('after_generate', filter.minifyXml, hexo.config.minify.xml.priority)
|
hexo.extend.filter.register('after_render:css', require('./lib/css').minifyCss, hexo.config.minify.css.priority)
|
||||||
hexo.extend.filter.register('after_generate', filter.minifyJson, hexo.config.minify.json.priority)
|
}
|
||||||
}
|
}
|
||||||
|
if (hexo.config.minify.js.enable === true) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
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}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
'use strict'
|
||||||
|
const CleanCSS = require('clean-css')
|
||||||
|
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
|
||||||
|
const options = hexo.config.minify.css
|
||||||
|
if (!str) return str
|
||||||
|
|
||||||
|
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).styles
|
||||||
|
if (verbose) logFn.call(this, str, styles, path, 'css')
|
||||||
|
return styles
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(`Path: ${path}\n${err}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @this {import('@types/hexo')}
|
||||||
|
*/
|
||||||
|
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}`)]
|
||||||
|
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,
|
||||||
|
minifyCssWithMap
|
||||||
|
}
|
344
lib/filter.js
344
lib/filter.js
|
@ -1,337 +1,11 @@
|
||||||
'use strict'
|
'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 = {
|
module.exports = {
|
||||||
minifyHtml,
|
... require('./html'),
|
||||||
minifyCss,
|
... require('./css'),
|
||||||
minifyJs,
|
... require('./js'),
|
||||||
minifySvg,
|
... require('./svg'),
|
||||||
gzipFn,
|
... require('./xml'),
|
||||||
brotliFn,
|
... require('./json'),
|
||||||
zstdFn,
|
... require('./zlib'),
|
||||||
minifyXml,
|
... require('./zstd')
|
||||||
minifyJson
|
}
|
||||||
}
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
'use strict'
|
||||||
|
const { minify: htmlMinify } = require('html-minifier-terser')
|
||||||
|
const { isMatch, logFn } = require('./tools')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} str
|
||||||
|
* @param {{ path: string }} data
|
||||||
|
* @this {import('@types/hexo')}
|
||||||
|
*/
|
||||||
|
async function minifyHtml(str, data) {
|
||||||
|
const hexo = this
|
||||||
|
const options = hexo.config.minify.html
|
||||||
|
if (!str) return str
|
||||||
|
|
||||||
|
const path = data.path
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
'use strict'
|
||||||
|
const { minify: terserMinify } = require('terser')
|
||||||
|
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
|
||||||
|
const options = hexo.config.minify.js
|
||||||
|
if (!str) return str
|
||||||
|
|
||||||
|
const path = data.path
|
||||||
|
const { exclude, globOptions, verbose, ...jsOptions } = options
|
||||||
|
|
||||||
|
if (isMatch(path, exclude, globOptions)) return str
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
try {
|
||||||
|
const code = await terserMinify(str, jsOptions).code
|
||||||
|
if (verbose) logFn.call(this, str, code, path, 'js')
|
||||||
|
return code
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(`Path: ${path}\n${err}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @this {import('@types/hexo')}
|
||||||
|
*/
|
||||||
|
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,
|
||||||
|
minifyJsWithMap
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
'use strict'
|
||||||
|
const { match, logFn } = require('./tools')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @this {import('@types/hexo')}
|
||||||
|
*/
|
||||||
|
function minifyJson() {
|
||||||
|
const hexo = this
|
||||||
|
const options = hexo.config.minify.json
|
||||||
|
|
||||||
|
const route = hexo.route
|
||||||
|
/** @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: void) => 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')
|
||||||
|
route.set(path, result)
|
||||||
|
} catch (err) {
|
||||||
|
reject(new Error(`Path: ${path}\n${err}`))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
minifyJson
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
'use strict'
|
||||||
|
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
|
||||||
|
|
||||||
|
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 = typeof options.plugins === '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: 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 data = svgOptimize(assetTxt, { ...options, plugins }).data
|
||||||
|
if (verbose) logFn.call(this, assetTxt, data, path, 'svg')
|
||||||
|
route.set(path, data)
|
||||||
|
} catch (err) {
|
||||||
|
reject(new Error(`Path: ${path}\n${err}`))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
minifySvg
|
||||||
|
}
|
|
@ -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.basename
|
||||||
|
// 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 = 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 {{ 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)
|
||||||
|
const log = this.log || console
|
||||||
|
log.log(`${ext}: ${path} [${saved}% saved]`)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
isMatch,
|
||||||
|
match,
|
||||||
|
logFn
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
'use strict'
|
||||||
|
const { match, logFn } = require('./tools')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @this {import('@types/hexo')}
|
||||||
|
*/
|
||||||
|
async function minifyXml() {
|
||||||
|
const { minify: compressXml } = await import('minify-xml')
|
||||||
|
|
||||||
|
const hexo = this
|
||||||
|
const options = hexo.config.minify.xml
|
||||||
|
|
||||||
|
const route = hexo.route
|
||||||
|
/** @type {string[]} */
|
||||||
|
const routeList = route.list()
|
||||||
|
const { globOptions, include, verbose } = options
|
||||||
|
|
||||||
|
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 = ''
|
||||||
|
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')
|
||||||
|
route.set(path, result)
|
||||||
|
} catch (err) {
|
||||||
|
reject(new Error(`Path: ${path}\n${err}`))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
minifyXml
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
'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')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @this {import('@types/hexo')}
|
||||||
|
*/
|
||||||
|
function gzipFn() {
|
||||||
|
const hexo = this
|
||||||
|
const options = hexo.config.minify.gzip
|
||||||
|
|
||||||
|
const route = hexo.route
|
||||||
|
/** @type {string[]} */
|
||||||
|
const routeList = route.list()
|
||||||
|
const { globOptions, include, verbose, ensureCompressed } = options
|
||||||
|
let level = options.level
|
||||||
|
if (typeof level !== 'number') level = zlib.constants.Z_BEST_COMPRESSION
|
||||||
|
|
||||||
|
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 result = await gzip(assetTxt, { level })
|
||||||
|
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}`))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @this {import('@types/hexo')}
|
||||||
|
*/
|
||||||
|
function brotliFn() {
|
||||||
|
const hexo = this
|
||||||
|
const options = hexo.config.minify.brotli
|
||||||
|
|
||||||
|
const route = hexo.route
|
||||||
|
/** @type {string[]} */
|
||||||
|
const routeList = route.list()
|
||||||
|
const { globOptions, include, verbose, ensureCompressed } = options
|
||||||
|
let level = options.level
|
||||||
|
if (typeof level !== 'number') level = zlib.constants.BROTLI_MAX_QUALITY
|
||||||
|
|
||||||
|
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 result = await br(assetTxt, { params: { [zlib.constants.BROTLI_PARAM_QUALITY]: level } })
|
||||||
|
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}`))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
gzipFn,
|
||||||
|
brotliFn
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {{ compress: (data: Buffer, level?: number) => Promise<Buffer>, init: () => Promise<void> }}
|
||||||
|
*/
|
||||||
|
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')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @this {import('@types/hexo')}
|
||||||
|
*/
|
||||||
|
async function zstdFn() {
|
||||||
|
const hexo = this
|
||||||
|
const options = hexo.config.minify.zstd
|
||||||
|
|
||||||
|
const route = hexo.route
|
||||||
|
/** @type {string[]} */
|
||||||
|
const routeList = route.list()
|
||||||
|
const { globOptions, include, verbose, ensureCompressed } = options
|
||||||
|
let level = options.level
|
||||||
|
if (typeof level !== 'number') level = undefined
|
||||||
|
|
||||||
|
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 = ''
|
||||||
|
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, input, result, path, 'zstd')
|
||||||
|
if (!ensureCompressed || input.length > result.length) {
|
||||||
|
route.set(path + '.zst', result)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
reject(new Error(`Path: ${path}\n${err}`))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
zstdFn
|
||||||
|
}
|
17
package.json
17
package.json
|
@ -29,16 +29,27 @@
|
||||||
"clean-css": "^5.1.2",
|
"clean-css": "^5.1.2",
|
||||||
"html-minifier-terser": "^7.2.0",
|
"html-minifier-terser": "^7.2.0",
|
||||||
"micromatch": "^4.0.2",
|
"micromatch": "^4.0.2",
|
||||||
"minify-xml": "^3.2.0",
|
"minify-xml": "^4.5.2",
|
||||||
"svgo": "^3.0.0",
|
"svgo": "^3.0.0",
|
||||||
"terser": "^5.3.0",
|
"terser": "^5.3.0"
|
||||||
"@mongodb-js/zstd": "^1.2.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"hexo": "^7.1.0",
|
"hexo": "^7.1.0",
|
||||||
"jest": "^29.1.2",
|
"jest": "^29.1.2",
|
||||||
"standard": "^17.0.0"
|
"standard": "^17.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": [
|
"keywords": [
|
||||||
"minify",
|
"minify",
|
||||||
"compress",
|
"compress",
|
||||||
|
|
Loading…
Reference in New Issue