feat(globbing): support disabling basename for each pattern in 'include:' option

This commit is contained in:
curbengh 2019-12-28 10:10:30 +00:00
parent 9d6b72422b
commit 50d80ccc70
No known key found for this signature in database
GPG Key ID: 21EA847C35D6E034
3 changed files with 144 additions and 35 deletions

View File

@ -58,9 +58,25 @@ minify:
- **globOptions** - [micromatch options](https://github.com/micromatch/micromatch#options) to customise how glob patterns match files. - **globOptions** - [micromatch options](https://github.com/micromatch/micromatch#options) to customise how glob patterns match files.
- Defaults to `{ basename: true }`, unless the pattern has a slash. - Defaults to `{ basename: true }`, unless the pattern has a slash.
- basename is disabled depending on each pattern. - basename is disabled depending on each pattern.
- When specifying an array of patterns, e.g. `exclude: ['*foo.html', '**/bar.html']`, basename applies to `'*foo.html'`, but not `'**/bar.html'`. - This means the following options would work,
``` yml
exclude:
- '*foo.html'
- '**/bar/*/*.html'
globOptions:
basename: true # default
```
- This behaviour doesn't apply to pattern that starts with `!` (negation).
- This limitation only applies to `include:` option used in svg, gzip and brotli.
- This means the following options would *not* work,
``` yml
include:
- '!foo.svg'
- '!**/bar/*/*.svg'
globOptions:
basename: true
```
- basename would stay disabled, if explicitly disabled in `globOptions:`. - basename would stay disabled, if explicitly disabled in `globOptions:`.
- However, basename option applies to all patterns in `include:`
For more options, see [HTMLMinifier](https://github.com/kangax/html-minifier). For more options, see [HTMLMinifier](https://github.com/kangax/html-minifier).

View File

@ -28,6 +28,30 @@ const isMatch = (path = '', patterns = [], options = {}) => {
return false 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) { function logFn (original, minified, path, ext) {
const saved = ((original.length - minified.length) / original.length * 100).toFixed(2) const saved = ((original.length - minified.length) / original.length * 100).toFixed(2)
const log = this.log || console const log = this.log || console
@ -106,11 +130,7 @@ function minifySvg () {
const routeList = route.list() const routeList = route.list()
const { globOptions, include, verbose } = options const { globOptions, include, verbose } = options
let includeString = include || '' return Promise.all((match(routeList, include, globOptions)).map((path) => {
if (include && Array.isArray(include)) includeString = include.join('')
if (includeString && includeString.includes('/')) globOptions.basename = false
return Promise.all((micromatch(routeList, include, globOptions)).map((path) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const assetPath = route.get(path) const assetPath = route.get(path)
let assetTxt = '' let assetTxt = ''
@ -141,11 +161,7 @@ function gzipFn () {
let { level } = options let { level } = options
if (typeof level !== 'number') level = zlib.constants.Z_BEST_COMPRESSION if (typeof level !== 'number') level = zlib.constants.Z_BEST_COMPRESSION
let includeString = include || '' return Promise.all((match(routeList, include, globOptions)).map((path) => {
if (include && Array.isArray(include)) includeString = include.join('')
if (includeString && includeString.includes('/')) globOptions.basename = false
return Promise.all((micromatch(routeList, include, globOptions)).map((path) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const assetPath = route.get(path) const assetPath = route.get(path)
let assetTxt = '' let assetTxt = ''
@ -176,11 +192,7 @@ function brotliFn () {
let { level } = options let { level } = options
if (typeof level !== 'number') level = zlib.constants.BROTLI_MAX_QUALITY if (typeof level !== 'number') level = zlib.constants.BROTLI_MAX_QUALITY
let includeString = include || '' return Promise.all((match(routeList, include, globOptions)).map((path) => {
if (include && Array.isArray(include)) includeString = include.join('')
if (includeString && includeString.includes('/')) globOptions.basename = false
return Promise.all((micromatch(routeList, include, globOptions)).map((path) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const assetPath = route.get(path) const assetPath = route.get(path)
let assetTxt = '' let assetTxt = ''

View File

@ -377,12 +377,12 @@ describe('svg', () => {
test('include - basename', async () => { test('include - basename', async () => {
hexo.config.minify.svg.include = 'bar.svg' hexo.config.minify.svg.include = 'bar.svg'
const fooPath = 'foo/bar.svg' const path = 'foo/bar.svg'
hexo.route.set(fooPath, input) hexo.route.set(path, input)
await s() await s()
const { data } = await new Svgo(hexo.config.minify.svg).optimize(input) const { data } = await new Svgo(hexo.config.minify.svg).optimize(input)
const output = hexo.route.get(fooPath) const output = hexo.route.get(path)
let result = '' let result = ''
output.on('data', (chunk) => (result += chunk)) output.on('data', (chunk) => (result += chunk))
output.on('end', () => { output.on('end', () => {
@ -392,18 +392,99 @@ describe('svg', () => {
test('include - slash in pattern', async () => { test('include - slash in pattern', async () => {
hexo.config.minify.svg.include = '**/foo/*.svg' hexo.config.minify.svg.include = '**/foo/*.svg'
const fooPath = 'blog/site/example/foo/bar.svg' const path = 'blog/site/example/foo/bar.svg'
hexo.route.set(fooPath, input) hexo.route.set(path, input)
await s() await s()
const { data } = await new Svgo(hexo.config.minify.svg).optimize(input) const { data } = await new Svgo(hexo.config.minify.svg).optimize(input)
const output = hexo.route.get(fooPath) const output = hexo.route.get(path)
let result = '' let result = ''
output.on('data', (chunk) => (result += chunk)) output.on('data', (chunk) => (result += chunk))
output.on('end', () => { output.on('end', () => {
expect(result).toBe(data) expect(result).toBe(data)
}) })
}) })
test('include - basename + slash', async () => {
hexo.route.remove(path)
const paths = [
'lorem/ipsum/dolor.svg',
'gravida/sociis/erat/ante.svg',
'aptent/elementum.svg',
'felis/blandit/cursus.svg'
]
hexo.config.minify.svg.include = [
'dolor.svg',
'**/sociis/**/*.svg'
]
paths.forEach((inpath) => {
hexo.route.set(inpath, input)
})
await s()
const { data } = await new Svgo(hexo.config.minify.svg).optimize(input)
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(data)
})
})
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.svg',
'gravida/sociis/erat/ante.svg',
'aptent/elementum.svg',
'felis/blandit/cursus.svg'
]
hexo.config.minify.svg.include = [
'!dolor.svg'
]
hexo.config.minify.svg.globOptions = {
basename: false
}
paths.forEach((inpath) => {
hexo.route.set(inpath, input)
})
await s()
const { data } = await new Svgo(hexo.config.minify.svg).optimize(input)
paths.forEach((inpath) => {
const output = hexo.route.get(inpath)
let result = ''
output.on('data', (chunk) => (result += chunk))
output.on('end', () => {
expect(result).toBe(data)
})
})
})
test('include - empty route', async () => {
hexo.route.remove(path)
const result = await s()
expect(result.length).toBe(0)
})
}) })
describe('gzip', () => { describe('gzip', () => {
@ -506,22 +587,22 @@ describe('gzip', () => {
test('include - basename', async () => { test('include - basename', async () => {
hexo.config.minify.gzip.include = 'bar.txt' hexo.config.minify.gzip.include = 'bar.txt'
const fooPath = 'foo/bar.txt' const path = 'foo/bar.txt'
hexo.route.set(fooPath, input) hexo.route.set(path, input)
await g() await g()
const result = hexo.route.get(fooPath.concat('.gz')) const result = hexo.route.get(path.concat('.gz'))
expect(result).toBeDefined() expect(result).toBeDefined()
}) })
test('include - slash in pattern', async () => { test('include - slash in pattern', async () => {
hexo.config.minify.gzip.include = '**/foo/*.txt' hexo.config.minify.gzip.include = '**/foo/*.txt'
const fooPath = 'blog/site/example/foo/bar.txt' const path = 'blog/site/example/foo/bar.txt'
hexo.route.set(fooPath, input) hexo.route.set(path, input)
await g() await g()
const result = hexo.route.get(fooPath.concat('.gz')) const result = hexo.route.get(path.concat('.gz'))
expect(result).toBeDefined() expect(result).toBeDefined()
}) })
@ -623,22 +704,22 @@ describe('brotli', () => {
test('include - basename', async () => { test('include - basename', async () => {
hexo.config.minify.brotli.include = 'bar.txt' hexo.config.minify.brotli.include = 'bar.txt'
const fooPath = 'foo/bar.txt' const path = 'foo/bar.txt'
hexo.route.set(fooPath, input) hexo.route.set(path, input)
await b() await b()
const result = hexo.route.get(fooPath.concat('.br')) const result = hexo.route.get(path.concat('.br'))
expect(result).toBeDefined() expect(result).toBeDefined()
}) })
test('include - slash in pattern', async () => { test('include - slash in pattern', async () => {
hexo.config.minify.brotli.include = '**/foo/*.txt' hexo.config.minify.brotli.include = '**/foo/*.txt'
const fooPath = 'blog/site/example/foo/bar.txt' const path = 'blog/site/example/foo/bar.txt'
hexo.route.set(fooPath, input) hexo.route.set(path, input)
await b() await b()
const result = hexo.route.get(fooPath.concat('.br')) const result = hexo.route.get(path.concat('.br'))
expect(result).toBeDefined() expect(result).toBeDefined()
}) })