diff --git a/README.md b/README.md index 443dbce..52bff76 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,14 @@ 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 +77,52 @@ async () => { }(); ``` +## Options + +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 +- 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/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 19d9003..4b47f0a 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, @@ -32,11 +32,16 @@ const Options = { * 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) => { +module.exports = (data, userOptions) => { assert(data); - return Minifier.minify(data, Options); + const options = { + ...defaultOptions, + ...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 a9d07ea..a3b6e58 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 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) { +async function optimize(file, userOptions) { check(file); const name = getName(file); @@ -57,24 +58,26 @@ 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 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) { +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; if (ext === 'css') - [, b64Optimize] = await tryToCatch(minify.img, filename, optimizedData); + [, b64Optimize] = await tryToCatch(minify.img, filename, optimizedData, userOptions); return b64Optimize || optimizedData; } 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(); });