From 8a85e74ff14d6f831eb5429dcee9b125ec10ed11 Mon Sep 17 00:00:00 2001 From: James Dawson Date: Mon, 13 Jan 2020 15:01:59 -0600 Subject: [PATCH 1/3] feature(minify) add ability to pass options to HTML minifier via javascript api --- README.md | 31 +++++++++++++++++++++++++++++-- lib/html.js | 13 +++++++++---- lib/minify.js | 21 ++++++++++++--------- 3 files changed, 50 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 443dbce..c38d4f6 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,12 @@ const hello="world";for(let l=0;l { - const [error, data] = await tryToCatch(minify, './client.js'); + const [error, data] = await tryToCatch(minify, './client.js', options); if (error) return console.error(error.message); @@ -65,6 +73,25 @@ async () => { }(); ``` +## Options + +The following options can be changed for HTML files. Defaults listed below: +- removeComments: true +- removeCommentsFromCDATA: true +- removeCDATASectionsFromCDATA: true +- collapseWhitespace: true +- collapseBooleanAttributes: true +- removeAttributeQuotes: true +- removeRedundantAttributes: true +- useShortDoctype: true +- removeEmptyAttributes: true +- removeEmptyElements: false +- removeOptionalTags: true +- removeScriptTypeAttributes: true +- removeStyleLinkTypeAttributes: true +- minifyJS: true +- minifyCSS: true + ## License MIT diff --git a/lib/html.js b/lib/html.js index 19d9003..00bece5 100644 --- a/lib/html.js +++ b/lib/html.js @@ -5,7 +5,7 @@ const assert = require('assert'); const Minifier = require('html-minifier'); -const Options = { +const defaultOptions = { removeComments: true, removeCommentsFromCDATA: true, removeCDATASectionsFromCDATA: true, @@ -34,9 +34,14 @@ const Options = { * @param data * @param callback */ -module.exports = (data) => { +module.exports = (data, userOptions) => { assert(data); - - return Minifier.minify(data, Options); + + const options = { + ...defaultOptions, + ...userOptions + } + + return Minifier.minify(data, options); }; diff --git a/lib/minify.js b/lib/minify.js index a9d07ea..8b61281 100644 --- a/lib/minify.js +++ b/lib/minify.js @@ -20,7 +20,7 @@ function check(name) { throw Error('name could not be empty!'); } -async function minify(name) { +async function minify(name, userOptions) { const EXT = ['js', 'html', 'css']; check(name); @@ -32,7 +32,7 @@ async function minify(name) { throw Error(`File type "${ext}" not supported.`); log('optimizing ' + path.basename(name)); - return optimize(name); + return optimize(name, userOptions); } function getName(file) { @@ -47,9 +47,10 @@ function getName(file) { /** * function minificate js,css and html files * - * @param files - js, css or html file path + * @param {string} file - js, css or html file path + * @param {object} userOptions - object of options to be combined with defaults and passed to the respective minifier */ -async function optimize(file) { +async function optimize(file, userOptions) { check(file); const name = getName(file); @@ -57,19 +58,21 @@ async function optimize(file) { log('reading file ' + path.basename(name)); const data = await readFile(name, 'utf8'); - return onDataRead(file, data); + return onDataRead(file, data, userOptions); } /** -* Processing of files -* @param fileData {name, data} + * Processing of files + * @param {string} filename + * @param {string} data - the contents of the file + * @param {object} userOptions - object of options to be combined with defaults and passed to the respective minifier */ -async function onDataRead(filename, data) { +async function onDataRead(filename, data, userOptions) { log('file ' + path.basename(filename) + ' read'); const ext = path.extname(filename).replace(/^\./, ''); - const optimizedData = await minify[ext](data); + const optimizedData = await minify[ext](data, userOptions); let b64Optimize; From 885d567385b695e1aceb0abbbc42ddc140c328e8 Mon Sep 17 00:00:00 2001 From: James Dawson Date: Wed, 15 Jan 2020 12:02:11 -0600 Subject: [PATCH 2/3] feature(minify) add ability to pass options into html, css, js, and img parsers --- README.md | 41 ++++++++++++++++++++++++++++++++++++----- lib/css.js | 7 +++++-- lib/html.js | 10 +++++----- lib/img.js | 16 +++++++++++----- lib/js.js | 7 +++++-- lib/minify.js | 6 +++--- 6 files changed, 65 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index c38d4f6..52bff76 100644 --- a/README.md +++ b/README.md @@ -43,8 +43,10 @@ const hello="world";for(let l=0;l { @@ -75,7 +79,34 @@ async () => { ## Options -The following options can be changed for HTML files. Defaults listed below: +The options object accepts configuration for `html`, `css`, `js`, and `img` like so: + +``` +const options = { + html: { + removeAttributeQuotes: false, + }, + css: { + compatibility: '*', + }, + js: { + ecma: 5, + }, + img: { + maxSize: 4096, + } +} +``` + +Full documentation for options that each file type accepts can be found on the pages of the libraries used by minify to process the files: +- HTML: https://github.com/kangax/html-minifier +- CSS: https://github.com/jakubpawlowicz/clean-css +- JS: https://github.com/terser/terser +- IMG: https://github.com/Filirom1/css-base64-images + +``` + +minify sets a few defaults for HTML that may differ from the base `html-minifier` settings: - removeComments: true - removeCommentsFromCDATA: true - removeCDATASectionsFromCDATA: true diff --git a/lib/css.js b/lib/css.js index ab5b7b7..a6ee536 100644 --- a/lib/css.js +++ b/lib/css.js @@ -9,14 +9,17 @@ const Clean = require('clean-css'); * minify css data. * * @param data + * @param userOptions - (optional) object that may contain a `css` key with an object of options */ -module.exports = (data) => { +module.exports = (data, userOptions) => { assert(data); + const options = userOptions && userOptions.css || {}; + const { styles, errors, - } = new Clean().minify(data); + } = new Clean(options).minify(data); const [error] = errors; diff --git a/lib/html.js b/lib/html.js index 00bece5..4b47f0a 100644 --- a/lib/html.js +++ b/lib/html.js @@ -32,16 +32,16 @@ const defaultOptions = { * minify html data. * * @param data - * @param callback + * @param userOptions - (optional) object that may contain an `html` key with an object of options */ module.exports = (data, userOptions) => { assert(data); - + const options = { ...defaultOptions, - ...userOptions - } - + ...userOptions && userOptions.html || {}, + }; + return Minifier.minify(data, options); }; diff --git a/lib/img.js b/lib/img.js index f8ee013..2c08ace 100644 --- a/lib/img.js +++ b/lib/img.js @@ -8,7 +8,9 @@ const fromString = promisify(require('css-b64-images').fromString); const ONE_KB = 2 ** 10; -const maxSize = 100 * ONE_KB; +const defaultOptions = { + maxSize: 100 * ONE_KB, +}; /** * minify css data. @@ -16,16 +18,20 @@ const maxSize = 100 * ONE_KB; * * @param name * @param data + * @param userOptions - (optional) object that may contain an `img` key with an object of options */ -module.exports = async (name, data) => { +module.exports = async (name, data, userOptions) => { const dir = path.dirname(name); const dirRelative = dir + '/../'; + const options = { + ...defaultOptions, + ...userOptions && userOptions.img || {}, + }; + assert(name); assert(data); - return fromString(data, dir, dirRelative, { - maxSize, - }); + return fromString(data, dir, dirRelative, options); }; diff --git a/lib/js.js b/lib/js.js index 6ff27f6..75df0d4 100644 --- a/lib/js.js +++ b/lib/js.js @@ -7,14 +7,17 @@ const assert = require('assert'); * minify js data. * * @param data + * @param userOptions - (optional) object that may contain a `js` key with an object of options */ -module.exports = (data) => { +module.exports = (data, userOptions) => { assert(data); + const options = userOptions && userOptions.js || {}; + const { error, code, - } = terser.minify(data); + } = terser.minify(data, options); if (error) throw error; diff --git a/lib/minify.js b/lib/minify.js index 8b61281..a3b6e58 100644 --- a/lib/minify.js +++ b/lib/minify.js @@ -48,7 +48,7 @@ function getName(file) { * function minificate js,css and html files * * @param {string} file - js, css or html file path - * @param {object} userOptions - object of options to be combined with defaults and passed to the respective minifier + * @param {object} userOptions - object with optional `html`, `css, `js`, and `img` keys, which each can contain options to be combined with defaults and passed to the respective minifier */ async function optimize(file, userOptions) { check(file); @@ -65,7 +65,7 @@ async function optimize(file, userOptions) { * Processing of files * @param {string} filename * @param {string} data - the contents of the file - * @param {object} userOptions - object of options to be combined with defaults and passed to the respective minifier + * @param {object} userOptions - object with optional `html`, `css, `js`, and `img` keys, which each can contain options to be combined with defaults and passed to the respective minifier */ async function onDataRead(filename, data, userOptions) { log('file ' + path.basename(filename) + ' read'); @@ -77,7 +77,7 @@ async function onDataRead(filename, data, userOptions) { let b64Optimize; if (ext === 'css') - [, b64Optimize] = await tryToCatch(minify.img, filename, optimizedData); + [, b64Optimize] = await tryToCatch(minify.img, filename, optimizedData, userOptions); return b64Optimize || optimizedData; } From 4511513271e2e5060f1225e3cea37aacefbf5c30 Mon Sep 17 00:00:00 2001 From: James Dawson Date: Tue, 21 Jan 2020 15:57:38 -0600 Subject: [PATCH 3/3] feature(minify) add unit tests for passing options to JS, CSS, HTML; correct tests that were double-minifying items; update naming conventions in tests --- test/minify.js | 85 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 72 insertions(+), 13 deletions(-) diff --git a/test/minify.js b/test/minify.js index de995e7..e66dc60 100644 --- a/test/minify.js +++ b/test/minify.js @@ -13,10 +13,30 @@ const htmlMinifier = require('html-minifier'); test('js', async (t) => { const js = 'function hello(world) {\nconsole.log(world);\n}'; - const data = await minify.js(js); - const min = terser.minify(data).code; + const minifyOutput = await minify.js(js); + const terserOutput = terser.minify(js).code; - t.equal(data, min, 'js output should be equal'); + t.equal(minifyOutput, terserOutput, 'js output should be equal'); + t.end(); +}); + +test('js: with alternate options', async (t) => { + const js = 'function isTrueFalse() { if (true !== false) { return true; } }'; + + const options = { + js: { + compress: { + booleans_as_integers: true, + }, + }, + }; + + const minifyOutputWithoutOptions = await minify.js(js); + const minifyOutput = await minify.js(js, options); + const terserOutput = terser.minify(js, options.js).code; + + t.equal(minifyOutput, terserOutput, 'js output should be equal'); + t.notEqual(minifyOutput, minifyOutputWithoutOptions, 'options should influence the output'); t.end(); }); @@ -46,32 +66,71 @@ test('html', async (t) => { minifyCSS: true, }; - const data = await minify.html(html); - const min = htmlMinifier.minify(data, options); + const minifyOutput = await minify.html(html); + const htmlMinifierOutput = htmlMinifier.minify(html, options); + + t.equal(minifyOutput, htmlMinifierOutput, 'html output should be equal'); + t.end(); +}); + +test('html: with alternate options', async (t) => { + const html = '\n\nhello world\n'; + + const options = { + html: { + removeOptionalTags: false, + }, + }; + + const minifyOutputWithoutOptions = await minify.html(html); + const minifyOutput = await minify.html(html, options); + const htmlMinifierOutput = htmlMinifier.minify(minifyOutput, options.html); - t.equal(data, min, 'html output should be equal'); + t.equal(minifyOutput, htmlMinifierOutput, 'html output should be equal'); + t.notEqual(minifyOutput, minifyOutputWithoutOptions, 'options should influence output'); t.end(); }); test('css', async (t) => { const css = 'color: #FFFFFF'; - const data = await minify.css(css); + const minifyOutput = await minify.css(css); const {styles} = new CleanCSS().minify(css); - t.equal(data, styles, 'css output should be equal'); + t.equal(minifyOutput, styles, 'css output should be equal'); + t.end(); +}); + +test('css: with alternate options', async (t) => { + const css = '.gradient { -ms-filter: \'progid:DXImageTransform.Microsoft.Gradient(startColorStr="#ffffff", endColorStr="#000000", GradientType=1)\'; background-image: linear-gradient(to right, #ffffff 0%, #000000 100%); }'; + const options = { + css: { + compatibility: { + properties: { + ieFilters: true, + }, + }, + }, + }; + + const minifyOutputWithoutOptions = await minify.css(css); + const minifyOutput = await minify.css(css, options); + const {styles} = new CleanCSS(options.css).minify(css); + + t.equal(minifyOutput, styles, 'css output should be equal'); + t.notEqual(minifyOutput, minifyOutputWithoutOptions, 'options should influence output'); t.end(); }); test('css: base64', async (t) => { const dir = `${__dirname}/fixtures`; - const name = `${dir}/style.css`; - const nameMin = `${dir}/style.min.css`; + const pathToCSS = `${dir}/style.css`; + const pathToMinifiedCSS = `${dir}/style.min.css`; - const min = fs.readFileSync(nameMin, 'utf8'); - const data = await minify(name); + const minifiedCSS = fs.readFileSync(pathToMinifiedCSS, 'utf8'); + const outputCSS = await minify(pathToCSS); - t.equal(data, min, 'should equal'); + t.equal(outputCSS, minifiedCSS, 'should be equal'); t.end(); });