feat: minify json

This commit is contained in:
curbengh 2020-01-03 01:16:38 +00:00
parent 64ba16cb22
commit fdcdbef4a1
No known key found for this signature in database
GPG Key ID: 21EA847C35D6E034
4 changed files with 254 additions and 3 deletions

View File

@ -7,7 +7,7 @@
[![Known Vulnerabilities](https://snyk.io/test/npm/hexo-yam/badge.svg)](https://snyk.io/test/npm/hexo-yam) [![Known Vulnerabilities](https://snyk.io/test/npm/hexo-yam/badge.svg)](https://snyk.io/test/npm/hexo-yam)
[![Greenkeeper badge](https://badges.greenkeeper.io/curbengh/hexo-yam.svg)](https://greenkeeper.io/) [![Greenkeeper badge](https://badges.greenkeeper.io/curbengh/hexo-yam.svg)](https://greenkeeper.io/)
Yet Another Minifier for Hexo. Minify and compress HTML, JS, CSS and SVG. XML, JSON and [many more](https://github.com/curbengh/hexo-yam/blob/ba77db0094a7c07ea9f70f010bfc15541d4105ca/index.js#L64) are also compressed. Support gzip and [brotli](https://en.wikipedia.org/wiki/Brotli) [compressions](https://en.wikipedia.org/wiki/HTTP_compression). Yet Another Minifier for Hexo. Minify and compress HTML, JS, CSS, SVG, XML and JSON. [Other files](https://github.com/curbengh/hexo-yam/blob/ba77db0094a7c07ea9f70f010bfc15541d4105ca/index.js#L64) are also compressed. Support gzip and [brotli](https://en.wikipedia.org/wiki/Brotli) [compressions](https://en.wikipedia.org/wiki/HTTP_compression).
## Table of contents ## Table of contents
@ -22,6 +22,7 @@ Yet Another Minifier for Hexo. Minify and compress HTML, JS, CSS and SVG. XML, J
- [Gzip](#gzip) - [Gzip](#gzip)
- [Brotli](#brotli) - [Brotli](#brotli)
- [XML](#xml) - [XML](#xml)
- [JSON](#json)
- [Globbing](#globbing) - [Globbing](#globbing)
- [HTTP Compression](#http-compression) - [HTTP Compression](#http-compression)
@ -194,6 +195,8 @@ minify:
## XML ## XML
Remove whitespaces in xml.
``` yaml ``` yaml
minify: minify:
xml: xml:
@ -210,6 +213,25 @@ minify:
- **removeComments** - Remove [comments](https://developer.mozilla.org/en-US/docs/Web/XML/XML_introduction) in xml. Defaults to `true`. - **removeComments** - Remove [comments](https://developer.mozilla.org/en-US/docs/Web/XML/XML_introduction) in xml. Defaults to `true`.
- **globOptions** - See [globbing](#globbing) section. - **globOptions** - See [globbing](#globbing) section.
## JSON
Remove whitespaces in json.
``` yaml
minify:
json:
enable: false
include:
- '*.json'
- '!*.min.json'
```
- **enable** - Enable the plugin. Defaults to `false`.
- **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.
- Exclude `*.min.json` by default.
- **globOptions** - See [globbing](#globbing) section.
## Globbing ## Globbing
Use "globOptions" to customise how glob patterns match files. Refer to [micromatch](https://github.com/micromatch/micromatch#options) for available options. Use "globOptions" to customise how glob patterns match files. Refer to [micromatch](https://github.com/micromatch/micromatch#options) for available options.

View File

@ -67,6 +67,13 @@ const xmlDefault = {
removeComments: true, removeComments: true,
globOptions: { basename: true } globOptions: { basename: true }
} }
const jsonDefault = {
enable: false,
priority: 10,
verbose: false,
include: ['*.json', '!*.min.json'],
globOptions: { basename: true }
}
hexo.config.minify = Object.assign(minifyDefault, hexo.config.minify) hexo.config.minify = Object.assign(minifyDefault, hexo.config.minify)
hexo.config.minify.html = Object.assign(htmlDefault, hexo.config.minify.html) hexo.config.minify.html = Object.assign(htmlDefault, hexo.config.minify.html)
@ -76,6 +83,7 @@ hexo.config.minify.svg = Object.assign(svgDefault, hexo.config.minify.svg)
hexo.config.minify.gzip = Object.assign(gzipDefault, hexo.config.minify.gzip) hexo.config.minify.gzip = Object.assign(gzipDefault, hexo.config.minify.gzip)
hexo.config.minify.brotli = Object.assign(brotliDefault, hexo.config.minify.brotli) hexo.config.minify.brotli = Object.assign(brotliDefault, hexo.config.minify.brotli)
hexo.config.minify.xml = Object.assign(xmlDefault, hexo.config.minify.xml) hexo.config.minify.xml = Object.assign(xmlDefault, hexo.config.minify.xml)
hexo.config.minify.json = Object.assign(jsonDefault, hexo.config.minify.json)
if (hexo.config.minify.enable === true) { if (hexo.config.minify.enable === true) {
const filter = require('./lib/filter') const filter = require('./lib/filter')
@ -86,6 +94,7 @@ if (hexo.config.minify.enable === true) {
hexo.extend.filter.register('after_generate', filter.gzipFn, hexo.config.minify.gzip.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.brotliFn, hexo.config.minify.brotli.priority)
hexo.extend.filter.register('after_generate', filter.minifyXml, hexo.config.minify.xml.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)
} }
module.exports = { module.exports = {
@ -96,5 +105,6 @@ module.exports = {
svgDefault, svgDefault,
gzipDefault, gzipDefault,
brotliDefault, brotliDefault,
xmlDefault xmlDefault,
jsonDefault
} }

View File

@ -253,6 +253,36 @@ function minifyXml () {
})) }))
} }
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(err))
}
}
resolve()
})
})
}))
}
module.exports = { module.exports = {
minifyHtml, minifyHtml,
minifyCss, minifyCss,
@ -260,5 +290,6 @@ module.exports = {
minifySvg, minifySvg,
gzipFn, gzipFn,
brotliFn, brotliFn,
minifyXml minifyXml,
minifyJson
} }

188
test/json.test.js Normal file
View File

@ -0,0 +1,188 @@
/* eslint-env jest */
'use strict'
const Hexo = require('hexo')
const hexo = new Hexo(__dirname)
global.hexo = hexo
const { jsonDefault } = require('../index')
const jsonFn = require('../lib/filter').minifyJson.bind(hexo)
const path = 'foo.json'
const input = '{\n\t"vitae": "hendrerit",\n\t"tristique": [\n\t\t"primis",\n\t\t"quam"\n\t]\n}'
const expected = '{"vitae":"hendrerit","tristique":["primis","quam"]}'
describe('xml', () => {
beforeEach(() => {
hexo.config.minify.json = Object.assign({}, jsonDefault)
// plugin is disabled by default
hexo.config.minify.json.enable = true
hexo.route.set(path, input)
})
afterEach(() => {
const routeList = hexo.route.list()
routeList.forEach((path) => hexo.route.remove(path))
})
test('default', async () => {
await jsonFn()
const output = hexo.route.get(path)
let result = ''
output.on('data', (chunk) => (result += chunk))
output.on('end', () => {
expect(result).toBe(expected)
})
})
test('disable', async () => {
hexo.config.minify.json.enable = false
const result = await jsonFn()
expect(result).toBeUndefined()
})
test('option - verbose', async () => {
hexo.config.minify.json.verbose = true
hexo.log.log = jest.fn()
await jsonFn()
expect(hexo.log.log.mock.calls[0][0]).toContain(`json: ${path}`)
})
test('invalid input', async () => {
hexo.route.set(path, 'foo')
try {
await jsonFn()
} catch (err) {
expect(err.message).toContain('SyntaxError')
}
})
test('empty file', async () => {
hexo.route.set(path, '')
const result = await jsonFn()
expect(result).toBeDefined()
expect(result[0]).toBeUndefined()
})
test('include - exclude *.min.json by default', async () => {
const path = 'foo.min.json'
hexo.route.set(path, input)
await jsonFn()
const output = hexo.route.get(path)
let result = ''
output.on('data', (chunk) => (result += chunk))
output.on('end', () => {
expect(result).toBe(input)
})
})
test('include - basename', async () => {
hexo.config.minify.json.include = 'bar.json'
const path = 'foo/bar.json'
hexo.route.set(path, input)
await jsonFn()
const output = hexo.route.get(path)
let result = ''
output.on('data', (chunk) => (result += chunk))
output.on('end', () => {
expect(result).toBe(expected)
})
})
test('include - slash in pattern', async () => {
hexo.config.minify.json.include = '**/lectus/**/*.json'
const path = 'eleifend/lectus/nullam/dapibus/netus.json'
hexo.route.set(path, input)
await jsonFn()
const output = hexo.route.get(path)
let result = ''
output.on('data', (chunk) => (result += chunk))
output.on('end', () => {
expect(result).toBe(expected)
})
})
test('include - basename + slash', async () => {
hexo.route.remove(path)
const paths = [
'lorem/ipsum/dolor.json',
'gravida/sociis/erat/ante.json',
'aptent/elementum.json',
'felis/blandit/cursus.json'
]
hexo.config.minify.json.include = [
'dolor.json',
'**/sociis/**/*.json'
]
paths.forEach((inpath) => {
hexo.route.set(inpath, input)
})
await jsonFn()
const minPaths = paths.slice(0, 2)
const unminPaths = paths.slice(2)
minPaths.forEach((inpath) => {
const output = hexo.route.get(inpath)
let result = ''
output.on('data', (chunk) => (result += chunk))
output.on('end', () => {
expect(result).toBe(expected)
})
})
unminPaths.forEach((inpath) => {
const output = hexo.route.get(inpath)
let result = ''
output.on('data', (chunk) => (result += chunk))
output.on('end', () => {
expect(result).toBe(input)
})
})
})
test('include - reverse pattern + basename disabled', async () => {
hexo.route.remove(path)
const paths = [
'lorem/ipsum/dolor.json',
'gravida/sociis/erat/ante.json',
'aptent/elementum.json',
'felis/blandit/cursus.json'
]
hexo.config.minify.json.include = [
'!dolor.json'
]
hexo.config.minify.json.globOptions = {
basename: false
}
paths.forEach((inpath) => {
hexo.route.set(inpath, input)
})
await jsonFn()
paths.forEach((inpath) => {
const output = hexo.route.get(inpath)
let result = ''
output.on('data', (chunk) => (result += chunk))
output.on('end', () => {
expect(result).toBe(expected)
})
})
})
test('include - empty route', async () => {
hexo.route.remove(path)
const result = await jsonFn()
expect(result.length).toBe(0)
})
})