From 50d80ccc701ab2366cea6fcb34ca9362f940787e Mon Sep 17 00:00:00 2001 From: curbengh <43627182+curbengh@users.noreply.github.com> Date: Sat, 28 Dec 2019 10:10:30 +0000 Subject: [PATCH] feat(globbing): support disabling basename for each pattern in 'include:' option --- README.md | 20 +++++++- lib/filter.js | 42 ++++++++++------ test/filter.test.js | 117 +++++++++++++++++++++++++++++++++++++------- 3 files changed, 144 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 95d9356..30e2ed2 100644 --- a/README.md +++ b/README.md @@ -58,9 +58,25 @@ minify: - **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. - 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:`. - - However, basename option applies to all patterns in `include:` For more options, see [HTMLMinifier](https://github.com/kangax/html-minifier). diff --git a/lib/filter.js b/lib/filter.js index 980a498..ecaac77 100644 --- a/lib/filter.js +++ b/lib/filter.js @@ -28,6 +28,30 @@ const isMatch = (path = '', patterns = [], options = {}) => { 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 @@ -106,11 +130,7 @@ function minifySvg () { const routeList = route.list() const { globOptions, include, verbose } = options - let includeString = include || '' - 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 Promise.all((match(routeList, include, globOptions)).map((path) => { return new Promise((resolve, reject) => { const assetPath = route.get(path) let assetTxt = '' @@ -141,11 +161,7 @@ function gzipFn () { let { level } = options if (typeof level !== 'number') level = zlib.constants.Z_BEST_COMPRESSION - let includeString = include || '' - 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 Promise.all((match(routeList, include, globOptions)).map((path) => { return new Promise((resolve, reject) => { const assetPath = route.get(path) let assetTxt = '' @@ -176,11 +192,7 @@ function brotliFn () { let { level } = options if (typeof level !== 'number') level = zlib.constants.BROTLI_MAX_QUALITY - let includeString = include || '' - 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 Promise.all((match(routeList, include, globOptions)).map((path) => { return new Promise((resolve, reject) => { const assetPath = route.get(path) let assetTxt = '' diff --git a/test/filter.test.js b/test/filter.test.js index 37f7b55..ec0aa8c 100644 --- a/test/filter.test.js +++ b/test/filter.test.js @@ -377,12 +377,12 @@ describe('svg', () => { test('include - basename', async () => { hexo.config.minify.svg.include = 'bar.svg' - const fooPath = 'foo/bar.svg' - hexo.route.set(fooPath, input) + const path = 'foo/bar.svg' + hexo.route.set(path, input) await s() 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 = '' output.on('data', (chunk) => (result += chunk)) output.on('end', () => { @@ -392,18 +392,99 @@ describe('svg', () => { test('include - slash in pattern', async () => { hexo.config.minify.svg.include = '**/foo/*.svg' - const fooPath = 'blog/site/example/foo/bar.svg' - hexo.route.set(fooPath, input) + const path = 'blog/site/example/foo/bar.svg' + hexo.route.set(path, input) await s() 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 = '' output.on('data', (chunk) => (result += chunk)) output.on('end', () => { 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', () => { @@ -506,22 +587,22 @@ describe('gzip', () => { test('include - basename', async () => { hexo.config.minify.gzip.include = 'bar.txt' - const fooPath = 'foo/bar.txt' - hexo.route.set(fooPath, input) + const path = 'foo/bar.txt' + hexo.route.set(path, input) await g() - const result = hexo.route.get(fooPath.concat('.gz')) + const result = hexo.route.get(path.concat('.gz')) expect(result).toBeDefined() }) test('include - slash in pattern', async () => { hexo.config.minify.gzip.include = '**/foo/*.txt' - const fooPath = 'blog/site/example/foo/bar.txt' - hexo.route.set(fooPath, input) + const path = 'blog/site/example/foo/bar.txt' + hexo.route.set(path, input) await g() - const result = hexo.route.get(fooPath.concat('.gz')) + const result = hexo.route.get(path.concat('.gz')) expect(result).toBeDefined() }) @@ -623,22 +704,22 @@ describe('brotli', () => { test('include - basename', async () => { hexo.config.minify.brotli.include = 'bar.txt' - const fooPath = 'foo/bar.txt' - hexo.route.set(fooPath, input) + const path = 'foo/bar.txt' + hexo.route.set(path, input) await b() - const result = hexo.route.get(fooPath.concat('.br')) + const result = hexo.route.get(path.concat('.br')) expect(result).toBeDefined() }) test('include - slash in pattern', async () => { hexo.config.minify.brotli.include = '**/foo/*.txt' - const fooPath = 'blog/site/example/foo/bar.txt' - hexo.route.set(fooPath, input) + const path = 'blog/site/example/foo/bar.txt' + hexo.route.set(path, input) await b() - const result = hexo.route.get(fooPath.concat('.br')) + const result = hexo.route.get(path.concat('.br')) expect(result).toBeDefined() })