htmlnano

    1.0.0 • Public • Published

    htmlnano

    npm version Build Status

    Modular HTML minifier, built on top of the PostHTML. Inspired by cssnano.

    The author of htmlnano is available for hire as a full stack web developer: https://kirillmaltsev.net/services

    Benchmark

    Website Source (KB) html-minifier@4.0.0 htmlnano@0.2.8
    stackoverflow.blog 78 72 66
    github.com 215 187 177
    en.wikipedia.org 78 73 72
    npmjs.com 29 25 25
    Avg. minify rate 0% 10% 13%

    Usage

    Gulp

    npm install --save-dev gulp-htmlnano
    const gulp = require('gulp');
    const htmlnano = require('gulp-htmlnano');
    const options = {
        removeComments: false
    };
    
    gulp.task('default', function() {
        return gulp
            .src('./index.html')
            .pipe(htmlnano(options))
            .pipe(gulp.dest('./build'));
    });

    Javascript

    const htmlnano = require('htmlnano');
    const options = {
        removeEmptyAttributes: false, // Disable the module "removeEmptyAttributes"
        collapseWhitespace: 'conservative' // Pass options to the module "collapseWhitespace"
    };
    // posthtml, posthtml-render, and posthtml-parse options
    const postHtmlOptions = {
        sync: true, // https://github.com/posthtml/posthtml#usage
        lowerCaseTags: true, // https://github.com/posthtml/posthtml-parser#options
        quoteAllAttributes: false, // https://github.com/posthtml/posthtml-render#options
    };
    
    htmlnano
        // "preset" arg might be skipped (see "Presets" section below for more info)
        // "postHtmlOptions" arg might be skipped
        .process(html, options, preset, postHtmlOptions)
        .then(function (result) {
            // result.html is minified
        })
        .catch(function (err) {
            console.error(err);
        });

    PostHTML

    Just add htmlnano as a final plugin:

    const posthtml = require('posthtml');
    const options = {
        removeComments: false, // Disable the module "removeComments"
        collapseWhitespace: 'conservative' // Pass options to the module "collapseWhitespace"
    };
    const posthtmlPlugins = [
        /* other PostHTML plugins */
    
        require('htmlnano')(options)
    ];
    
    const postHtmlOptions = {
        // See PostHTML docs
    };
    
    posthtml(posthtmlPlugins)
        .process(html, posthtmlOptions)
        .then(function (result) {
            // result.html is minified
        })
        .catch(function (err) {
            console.error(err);
        });

    Presets

    A preset is just an object with modules config.

    Currently the following presets are available:

    • safe — a default preset for minifying a regular HTML in a safe way (without breaking anything)
    • ampSafe - same as safe preset but for AMP pages
    • max - maximal minification (might break some pages)

    You can use them the following way:

    const htmlnano = require('htmlnano');
    const ampSafePreset = require('htmlnano').presets.ampSafe;
    const options = {
        // Your options
    };
    
    htmlnano
        .process(html, options, ampSafePreset)
        .then(function (result) {
            // result.html is minified
        })
        .catch(function (err) {
            console.error(err);
        });

    If you skip preset argument safe preset would be used by default.

    If you'd like to define your very own config without any presets pass an empty object as a preset:

    const htmlnano = require('htmlnano');
    const options = {
        // Your options
    };
    
    htmlnano
        .process(html, options, {})
        .then(function (result) {
            // result.html is minified
        })
        .catch(function (err) {
            console.error(err);
        });

    You might create also your own presets:

    const htmlnano = require('htmlnano');
    // Preset for minifying email templates
    const emailPreset = {
        mergeStyles: true,
        minifyCss: {
            safe: true
        },
    };
    
    const options = {
        // Some specific options
    };
    
    htmlnano
        .process(html, options, emailPreset)
        .then(function (result) {
            // result.html is minified
        })
        .catch(function (err) {
            console.error(err);
        });

    Feel free to submit a PR with your preset if it might be useful for other developers as well.

    Modules

    By default the modules should only perform safe transforms, see the module documentation below for details. You can disable modules by passing false as option, and enable them by passing true.

    collapseAttributeWhitespace

    Collapse redundant white spaces in list-like attributes (class, rel, ping).

    Example

    Source:

    <div class=" content  page  "></div>

    Minified:

    <div class="content page"></div>

    collapseWhitespace

    Collapses redundant white spaces (including new lines). It doesn’t affect white spaces in the elements <style>, <textarea>, <script> and <pre>.

    Options
    • conservative — collapses all redundant white spaces to 1 space (default)
    • aggressive — collapses all whitespaces that are redundant and safe to remove
    • all — collapses all redundant white spaces
    Side effects

    all <i>hello</i> <i>world</i> or <i>hello</i><br><i>world</i> after minification will be rendered as helloworld. To prevent that use either the default conservative option, or the aggressive option.

    Example

    Source:

    <div>
        hello  world!
        <a href="#">answer</a>
        <style>div  { color: red; }  </style>
        <main></main>
    </div>

    Minified (with all):

    <div>hello world!<a href="#">answer</a><style>div  { color: red; }  </style><main></main></div>

    Minified (with aggressive):

    <div> hello world! <a href="#">answer</a> <style>div  { color: red; }  </style><main></main></div>

    Minified (with conservative):

    <div> hello world! <a href="#">answer</a> <style>div  { color: red; }  </style> <main></main> </div>

    deduplicateAttributeValues

    Remove duplicate values from list-like attributes (class, rel, ping).

    Example

    Source:

    <div class="sidebar left sidebar"></div>

    Minified:

    <div class="sidebar left"></div>

    removeComments

    Options
    • safe – removes all HTML comments except the conditional comments and <!--noindex--><!--/noindex--> (default)
    • all — removes all HTML comments
    Example

    Source:

    <div><!-- test --></div>

    Minified:

    <div></div>

    removeEmptyAttributes

    Removes empty safe-to-remove attributes.

    Side effects

    This module could break your styles or JS if you use selectors with attributes:

    img[style=""] {
        margin: 10px;
    }
    Example

    Source:

    <img src="foo.jpg" alt="" style="">

    Minified:

    <img src="foo.jpg" alt="">

    removeAttributeQuotes

    Remove quotes around attributes when possible, see HTML Standard - 12.1.2.3 Attributes - Unquoted attribute value syntax.

    Example

    Source:

    <div class="foo" title="hello world"></div>

    Minified:

    <div class=foo title="hello world"></div>
    Notice

    The feature is implemented by posthtml-render's quoteAllAttributes, which is one of the PostHTML's option. So removeAttributeQuotes could be overriden by other PostHTML's plugins and PostHTML's configuration.

    For example:

    posthtml([
        htmlnano({
            removeAttributeQuotes: true
        })
    ]).process(html, {
        quoteAllAttributes: true
    })

    removeAttributeQuotes will not work because PostHTML's quoteAllAttributes takes the priority.

    removeUnusedCss

    Removes unused CSS inside <style> tags with either uncss or PurgeCSS.

    With uncss

    Options

    See the documentation of uncss for all supported options.

    uncss options can be passed directly to the removeUnusedCss module:

    htmlnano.process(html, {
        removeUnusedCss: {
            ignore: ['.do-not-remove']
        }
    });

    The following uncss options are ignored if passed to the module:

    • stylesheets
    • ignoreSheets
    • raw

    With PurgeCSS

    Use PurgeCSS instead of uncss by adding tool: 'purgeCSS' to the options.

    Options

    See the documentation of PurgeCSS for all supported options.

    PurgeCSS options can be passed directly to the removeUnusedCss module:

    htmlnano.process(html, {
        removeUnusedCss: {
            tool: 'purgeCSS',
            safelist: ['.do-not-remove']
        }
    });

    The following PurgeCSS options are ignored if passed to the module:

    • content
    • css
    • extractors
    Example

    Source:

    <div class="b">
        <style>
            .a {
                margin: 10px 10px 10px 10px;
            }
            .b {
                color: #ff0000;
            }
        </style>
    </div>

    Optimized:

    <div class="b">
        <style>
            .b {
                color: #ff0000;
            }
        </style>
    </div>

    minifyCss

    Minifies CSS with cssnano inside <style> tags and style attributes.

    Options

    See the documentation of cssnano for all supported optimizations. By default CSS is minified with preset default, which shouldn't have any side-effects.

    To use another preset or disabled some optimizations pass options to minifyCss module:

    htmlnano.process(html, {
        minifyCss: {
            preset: ['default', {
                discardComments: {
                    removeAll: true,
                },
            }]
        }
    });
    Example

    Source:

    <div>
        <style>
            h1 {
                margin: 10px 10px 10px 10px;
                color: #ff0000;
            }
        </style>
    </div>

    Minified:

    <div>
        <style>h1{margin:10px;color:red}</style>
    </div>

    minifyJs

    Minifies JS using Terser inside <script> tags.

    Options

    See the documentation of Terser for all supported options. Terser options can be passed directly to the minifyJs module:

    htmlnano.process(html, {
        minifyJs: {
            output: { quote_style: 1 },
        },
    });
    Example

    Source:

    <div>
        <script>
            /* comment */
            const foo = function () {
    
            };
        </script>
    </div>

    Minified:

    <div>
        <script>const foo=function(){};</script>
    </div>

    minifyJson

    Minifies JSON inside <script type="application/json"></script>.

    Example

    Source:

    <script type="application/json">
    {
        "user": "me"
    }
    </script>

    Minified:

    <script type="application/json">{"user":"me"}</script>

    minifySvg

    Minifies SVG inside <svg> tags using SVGO.

    Options

    See the documentation of SVGO for all supported options. SVGO options can be passed directly to the minifySvg module:

    htmlnano.process(html, {
        minifySvg: {
            plugins: extendDefaultPlugins([
                {
                    name: 'builtinPluginName',
                    params: {
                        optionName: 'optionValue'
                    }
                }
            ])
        }
    });
    Example

    Source:

    <svg version="1.1" baseProfile="full" width="300" height="200" xmlns="http://www.w3.org/2000/svg">
        <rect width="100%" height="100%" fill="red" />
    
        <circle cx="150" cy="100" r="80" fill="green" />
    
        <text x="150" y="125" font-size="60" text-anchor="middle" fill="white">SVG</text>
    </svg>`

    Minified:

    <svg baseProfile="full" width="300" height="200" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="red"/><circle cx="150" cy="100" r="80" fill="green"/><text x="150" y="125" font-size="60" text-anchor="middle" fill="#fff">SVG</text></svg>

    minifyConditionalComments

    Minify content inside conditional comments.

    Example

    Source:

    <!--[if lte IE 7]>
    <style type="text/css">
    .title {
      color: red;
    }
    </style>
    <![endif]-->

    Minified:

    <!--[if lte IE 7]><style>.title{color:red}</style><![endif]-->

    removeRedundantAttributes

    Removes redundant attributes from tags if they contain default values:

    • method="get" from <form>
    • type="text" from <input>
    • type="submit" from <button>
    • language="javascript" and type="text/javascript" from <script>
    • charset from <script> if it's an external script
    • media="all" from <style> and <link>
    • type="text/css" from <link rel="stylesheet">
    Options

    This module is disabled by default, change option to true to enable this module.

    Side effects

    This module could break your styles or JS if you use selectors with attributes:

    form[method="get"] {
        color: red;
    }
    Example

    Source:

    <form method="get">
        <input type="text">
    </form>

    Minified:

    <form>
        <input>
    </form>

    collapseBooleanAttributes

    Collapses boolean attributes (like disabled) to the minimized form.

    Options

    If your document uses AMP, set the amphtml flag to collapse additonal, AMP-specific boolean attributes:

    "collapseBooleanAttributes": {
        "amphtml": true
    }
    Side effects

    This module could break your styles or JS if you use selectors with attributes:

    button[disabled="disabled"] {
        color: red;
    }
    Example

    Source:

    <button disabled="disabled">click</button>
    <script defer=""></script>

    Minified:

    <button disabled>click</button>
    <script defer></script>

    mergeStyles

    Merges multiple <style> with the same media and type into one tag. <style scoped>...</style> are skipped.

    Example

    Source:

    <style>h1 { color: red }</style>
    <style media="print">div { color: blue }</style>
    
    <style type="text/css" media="print">a {}</style>
    <style>div { font-size: 20px }</style>

    Minified:

    <style>h1 { color: red } div { font-size: 20px }</style>
    <style media="print">div { color: blue } a {}</style>

    mergeScripts

    Merge multiple <script> with the same attributes (id, class, type, async, defer) into one (last) tag.

    Side effects

    It could break your code if the tags with different attributes share the same variable scope. See the example below.

    Example

    Source:

    <script>const foo = 'A:1';</script>
    <script class="test">foo = 'B:1';</script>
    <script type="text/javascript">foo = 'A:2';</script>
    <script defer>foo = 'C:1';</script>
    <script>foo = 'A:3';</script>
    <script defer="defer">foo = 'C:2';</script>
    <script class="test" type="text/javascript">foo = 'B:2';</script>

    Minified:

    <script>const foo = 'A:1'; foo = 'A:2'; foo = 'A:3';</script>
    <script defer="defer">foo = 'C:1'; foo = 'C:2';</script>
    <script class="test" type="text/javascript">foo = 'B:1'; foo = 'B:2';</script>

    custom

    It's also possible to pass custom modules in the minifier. As a function:

    const options = {
        custom: function (tree, options) {
            // Some minification
            return tree;
        }
    };

    Or as a list of functions:

    const options = {
        custom: [
            function (tree, options) {
                // Some minification
                return tree;
            },
    
            function (tree, options) {
                // Some other minification
                return tree;
            }
        ]
    };

    options is an object with all options that were passed to the plugin.

    sortAttributesWithLists

    Sort values in list-like attributes (class, rel, ping).

    The module won't impact the plain-text size of the output. However it will improve the compression ratio of gzip/brotli used in HTTP compression.

    Options
    • alphabetical: Default option. Sort attribute values in alphabetical order.
    • frequency: Sort attribute values by frequency.
    Example

    alphabetical

    Source:

    <div class="foo baz bar">click</div>

    Processed:

    <div class="bar baz foo">click</div>

    frequency

    Source:

    <div class="foo baz bar"></div><div class="bar foo"></div>

    Processed:

    <div class="foo bar baz"></div><div class="foo bar"></div>

    sortAttributes

    Sort attributes inside elements.

    The module won't impact the plain-text size of the output. However it will improve the compression ratio of gzip/brotli used in HTTP compression.

    Options
    • alphabetical: Default option. Sort attributes in alphabetical order.
    • frequency: Sort attributes by frequency.
    Example

    alphabetical

    Source:

    <input type="text" class="form-control" name="testInput" autofocus="" autocomplete="off" id="testId">

    Processed:

    <input autocomplete="off" autofocus="" class="form-control" id="testId" name="testInput" type="text">

    frequency

    Source:

    <input type="text" class="form-control" name="testInput" id="testId">
    <a id="testId" href="#" class="testClass"></a>
    <img width="20" src="../images/image.png" height="40" alt="image" class="cls" id="id2">

    Processed:

    <input class="form-control" id="testId" type="text" name="testInput">
    <a class="testClass" id="testId" href="#"></a>
    <img class="cls" id="id2" width="20" src="../images/image.png" height="40" alt="image">

    minifyUrls

    Convert absolute URL to relative URL using relateurl.

    Options

    The base URL to resolve against. Support String & URL.

    htmlnano.process(html, {
        minifyUrls: 'https://example.com' // Valid configuration
    });
    htmlnano.process(html, {
        minifyUrls: new URL('https://example.com') // Valid configuration
    });
    htmlnano.process(html, {
        minifyUrls: false // The module will be disabled
    });
    htmlnano.process(html, {
        minifyUrls: true // Invalid configuration, the module will be disabled
    });
    Example

    Basic Usage

    Configuration:

    htmlnano.process(html, {
        minifyUrls: 'https://example.com'
    });

    Source:

    <a href="https://example.com/foo/bar/baz">bar</a>

    Minified:

    <a href="foo/bar/baz">bar</a>

    With sub-directory

    Configuration:

    htmlnano.process(html, {
        minifyUrls: 'https://example.com/foo/baz/'
    });

    Source:

    <a href="https://example.com/foo/bar">bar</a>

    Minified:

    <a href="../bar">bar</a>

    removeOptionalTags

    Remove certain tags that can be omitted, see HTML Standard - 13.1.2.4 Optional tags.

    Example

    Source:

    <html><head><title>Title</title></head><body><p>Hi</p></body></html>

    Minified:

    <title>Title</title><p>Hi</p>
    Notice

    Due to the limitation of PostHTML, htmlnano can't remove only the start tag or the end tag of an element. Currently, htmlnano only supports removing the following optional tags, as htmlnano can remove their start tag and end tag at the same time:

    • html
    • head
    • body
    • colgroup
    • tbody

    Contribute

    Since the minifier is modular, it's very easy to add new modules:

    1. Create a ES6-file inside lib/modules/ with a function that does some minification. For example you can check lib/modules/example.es6.

    2. Add the module's name into one of those presets. You can choose either ampSafe, max, or safe.

    3. Create a JS-file inside test/modules/ with some unit-tests.

    4. Describe your module in the section "Modules".

    5. Send me a pull request.

    Other types of contribution (bug fixes, documentation improves, etc) are also welcome! Would like to contribute, but don't have any ideas what to do? Check out our issues.

    Install

    npm i htmlnano

    DownloadsWeekly Downloads

    172,742

    Version

    1.0.0

    License

    MIT

    Unpacked Size

    87.9 kB

    Total Files

    33

    Last publish

    Collaborators

    • avatar
    • avatar