Minifies HTML/CSS: valid or broken, pure or mixed with other languages

§ Quick Take

import { strict as assert } from "assert";
import { crush, defaults, version } from "html-crush";

    `<table width="100" border="0" cellpadding="0" cellspacing="0">
    { removeLineBreaks: true }
  `<table width="100" border="0" cellpadding="0" cellspacing="0"><tr><td> hi

§ Two Modes

  1. Only remove indentations (the opposite of tabifier opens in a new tab)
  2. Fully minify, removing all whitespace between tags

§ Features

This program:

  • Does not parse the input — input can be (X)HTML or whatever or mixed with whatever
  • Equally, the input can be with HTML errors, broken HTML, incomplete HTML or not-quite-HTML or whatever
  • Mailchimp, Responsys, Exact Target, Campaign Monitor tags in your HTML - all fine

As a side priority, this application also takes into consideration human-friendliness:

  1. Its API (this npm library) reports progress and its GUI front-end utilises it to allow a responsive UI
  2. We deliberately keep options count under opens in a new tab
  3. GUI also considers white and dark interfaces, use of modern toggle switches, CSS hovers to react to any interaction
  4. API (this library) considers giving all possible JavaScript use choices: CommonJS transpiled to ES5, modern untranspiled ES Modules code in ES6, and UMD transpiled to ES5 with all dependencies baked-in, all published to npm and accessible via jsDelivr opens in a new tab CDN
  5. Developer friendliness - source is fully set up with console.logs which report the line numbers and all actions as they happen. Production builds (dist/) strip all logging, of course. This means it's easy to come back later or the first time and debug the code


This program exports a plain object where main function is under a key "crush". That's why you consume it like this:

import { crush, defaults, version } from "html-crush";
Exported KeyDescription
crushThe main function
defaultsOptional Options Object's defaults
versionAs per current package.json — a string, for example, "2.0.9"

§ API - crush() - Input

crush(str, [opts])

In other words, a function with two input arguments:

Input argument positionWe call itTypeObligatory?Description
firststrStringyesThe input string of zero or more characters
secondoptsPlain objectnoAn Optional Options Object. See below for its API.

If supplied input arguments are of any other types, an error will be thrown.

§ API - crush() - Output

The function exported under key crush will return a plain object where you'll find: the log data, result string and corresponding string ranges of all actions performed:

Key's nameKey value's typeDescription
logPlain objectFor example, { timeTakenInMilliseconds: 6, originalLength: 0, cleanedLength: 0, bytesSaved: 0, percentageReducedOfOriginal: 0 }
rangesArray of zero or more string range arraysResult in ranges notation.
applicableOptsPlain objectReports which options would have made a difference if enabled or disabled, considering the given input.
resultStringThe result as string.

For example,

log: {
timeTakenInMilliseconds: 7,
originalLength: 104,
cleanedLength: 92,
bytesSaved: 12,
percentageReducedOfOriginal: 12
ranges: null,
applicableOpts: { removeHTMLComments: false, removeCSSComments: false },
result: '<table width="100" border="0" cellpadding="0" cellspacing="0"><tr><td> hi\n' +

§ API - crush() - Optional Options Object

Options Object's keyThe type of its valueDefaultDescription
lineLengthLimitnumber500When removing line breaks, what is the maximum line length to keep. Relevant only when opts.removeLineBreaks is on
removeIndentationsBooleantrueShould we remove indentations? The default is, yes.
removeLineBreaksBooleanfalseShould we remove the line breaks? The default answer is, no. Enabling it automatically enables opts.removeIndentations.
removeHTMLCommentsBoolean or Numbers: 0, 1 or 2falseShould we remove the HTML comments? Default answer, false is no, but there are settings to remove comments: 0 is off, 1 instructs to remove non-Outlook comments, 2 removes all comments including Outlook conditionals
removeCSSCommentsBooleantrueShould we remove CSS comments? This concerns both head CSS comments and inline CSS style comments within HTML style attributes.
reportProgressFuncnull or Boolean false or functionnullIf you supply a function here, it will be called, and an argument will be given to it, a natural number, which means percentage complete at that moment. Values will range from 1 to 99, and finally, the main function will return the result's plain object.
reportProgressFuncFromNatural number0Default is zero percent but you can squeeze reporting percentages to start from a different number
reportProgressFuncToNatural number100Default is 100 percent but you can squeeze reporting percentages to go up to a different number
breakToTheLeftOfarray of zero or more stringssee the list belowWhen any of given strings are encountered AND removeLineBreaks option is on, current line will be terminated. This setting is not active if removeLineBreaks is turned off. If you want to disable a default set, either set this key to false or null or to an empty array.
mindTheInlineTagsarray of zero or more stringssee the list belowInline HTML tags such as <span> can accidentally introduce extra text. We take extra precautions when minifying around inline tags.

Here it is, in one place, in case you want to copy-paste it somewhere:

lineLengthLimit: 500,
removeIndentations: true,
removeLineBreaks: false,
removeHTMLComments: false,
removeCSSComments: true,
reportProgressFunc: null,
reportProgressFuncFrom: 0,
reportProgressFuncTo: 100,
breakToTheLeftOf: [
mindTheInlineTags: [

§ API - crush() - opts.reportProgressFunc

This feature is used in a web worker opens in a new tab setup. Basically, you pass the web worker the input (source, options) and it passes you one or more messages back. That can be one message, final result, but it can equally be many messages, for example, a sequence of natural numbers, each meaning progress percentage done so far, AND THEN, finally, full result.

This latter case is exactly what is happening on our front-end GUI,

If you set the optional options object's key's reportProgressFunc value to anything else than a function, an error will be thrown. If you set it to a function, that function will be fed a natural number string, meaning percentage done so far, from 1 to 100.

Now, it's up to you how to distinguish "in progress" results and the final result. We use a random string, which is unlikely to happen in the input and we append that secret random string in front of the percentage being passed. Then, front-end checks did result that came through have a secret random string in front or not. If so, it's progress. If not, it's a final result.

§ Licence

MIT opens in a new tab

Copyright © 2010–2020 Roy Revelt and other contributors

Related articles:

Related packages:

📦 email-comb 3.10.5
Remove unused CSS from email templates
📦 ranges-apply 3.2.3
Take an array of string index ranges, delete/replace the string according to them
📦 emlint 2.19.2
Pluggable email template code linter
📦 string-strip-html 6.2.0
Strips HTML tags from strings. No parser, accepts mixed sources
📦 detect-is-it-html-or-xhtml 3.10.0
Answers, is the string input string more an HTML or XHTML (or neither)
📦 html-table-patcher 2.0.14
Visual helper to place templating code around table tags into correct places
📦 is-html-tag-opening 1.8.3
Is given opening bracket a beginning of a tag?