Installation
Quick Take
Examples
- Minifies and uglifies
- Returns all extracted and deleted classes and id's
- Works even if
<style>
is within<body>
and there's no<head>
- Whitelist using wildcards
API — comb()
It is a function which takes two arguments: a string and an optional options object:
function comb(str: string, opts?: Partial<Opts>): Res;
The optional options object has the following shape:
{
whitelist: string[];
backend: HeadsAndTailsObj[];
uglify: boolean;
removeHTMLComments: boolean;
removeCSSComments: boolean;
doNotRemoveHTMLCommentsWhoseOpeningTagContains: string[];
htmlCrushOpts: {
lineLengthLimit: number;
removeIndentations: boolean;
removeLineBreaks: boolean;
removeHTMLComments: boolean | 0 | 1 | 2;
removeCSSComments: boolean;
reportProgressFunc: null | ((percDone: number) => void);
reportProgressFuncFrom: number;
reportProgressFuncTo: number;
breakToTheLeftOf: string[];
mindTheInlineTags: string[];
};
reportProgressFunc: null | ((percDone: number) => void);
reportProgressFuncFrom: number;
reportProgressFuncTo: number;
}
Key | Type | Default | Example | Description |
---|---|---|---|---|
whitelist Type: Array Default: [] Example: [".class-1", "#id-1", ".module-*"] | ||||
whitelist | Array | [] | [".class-1", "#id-1", ".module-*"] | List all classes or id’s you want this library to ignore. You can use all matcher patterns. |
backend Type: Array Default: [] Example: [{ heads: "{{", tails: "}}" }, { heads: "{{%", tails: "%}}" }] | ||||
backend | Array | [] | [{ heads: "{{", tails: "}}" }, { heads: "{{%", tails: "%}}" }] | If your code has back-end code within clss or id values, for example, class="{{ red }} main-box" you can stop {{ , red and }} to be treated as class names |
uglify Type: Boolean Default: false Example: n/a | ||||
uglify | Boolean | false | n/a | Will rename all class and id names to be few characters-long. This might reduce your file size by another kilobyte. |
removeHTMLComments Type: Boolean Default: true Example: n/a | ||||
removeHTMLComments | Boolean | true | n/a | When enabled, all HTML comments (<!-- to --> ) will be removed |
removeCSSComments Type: Boolean Default: true Example: n/a | ||||
removeCSSComments | Boolean | true | n/a | When enabled, all CSS comments (/* to */ ) will be removed |
doNotRemoveHTMLCommentsWhoseOpeningTagContains Type: Array of zero or more insensitive strings Default: ["[if", "[endif"] Example: n/a | ||||
doNotRemoveHTMLCommentsWhoseOpeningTagContains | Array of zero or more insensitive strings | ["[if", "[endif"] | n/a | Email code often contains Outlook or IE conditional comments which you probably don’t want to remove. Whatever strings you list here, if comment’s opening tag will contain these, that tag will not be removed. |
htmlCrushOpts Type: Plain object Default: see below Example: n/a | ||||
htmlCrushOpts | Plain object | see below | n/a | Allows to customise the minification settings. See more on html-crush documentation |
reportProgressFunc Type: Function or something falsy Default: null Example: n/a | ||||
reportProgressFunc | Function or something falsy | null | n/a | If supplied, it will ping the function you assign passing current percentage done (natural number) as an input argument |
reportProgressFuncFrom Type: Natural number Default: 0 Example: n/a | ||||
reportProgressFuncFrom | Natural number | 0 | n/a | By default, percentages are reported from 0 to 100. This value overrides this starting percentage value. |
reportProgressFuncTo Type: Natural number Default: 100 Example: n/a | ||||
reportProgressFuncTo | Natural number | 100 | n/a | By default, percentages are reported from 0 to 100. This value overrides this ending percentage value. |
Here are all defaults in one place for copying:
The function returns a plain object (marked as type Res
):
API — defaults
You can import defaults
:
It's a plain object:
The main function calculates the options to be used by merging the options you passed with these defaults.
API — version
You can import version
:
opts.whitelist
Since the main purpose of this library is to clean email HTML, it needs to cater for email code specifics. One of them is that CSS styles will contain fix or hack styles, meant for email software. For example, here are few of them:
#outlook a {
padding: 0;
}
.ReadMsgBody {
width: 100%;
}
You will not be using these classes within the <body>
of your HTML code, so they would get removed as “unused” because they are present in <head>
only. To avoid that, pass the classes, and id
’s in the whitelist key’s value, as an array. For example:
var html = "<!DOCTYPE html>...";
comb(html, {
whitelist: ["#outlook", ".ExternalClass", ".ReadMsgBody"],
});
It’s a common practice to name each email template module (the outermost table) using a class. For example, <table class="module-header"...
, <table class="module-promo1"...
and so on. To prevent these classes from being removed, use a wildcard:
var html = "<!DOCTYPE html>...";
comb(html, {
whitelist: [".module-*"],
});
opts.backend
Email template source code typically contains some templating language literals, for example Jinja/Nunjucks:
<td class="mt10 {{ module.on }} module-box blackbg">
How do we prevent {{
, module.on
and }}
from being treated as class names?
You need to let email-comb
know about these markings. We call opening-ones “heads” and closing-ones “tails”.
For example, in Mailchimp piece of code *|tralala|*
, we’d call the *|
bit “heads” and |*
“tails”.
You need to pass pairs of “heads” and “tails” to email-comb
, for example, here’s Nunjucks/Jinja code:
import { comb } from "email-comb";
const res = comb(
`<!doctype html>
<html>
<head>
<style>
.aaa {
color: black;
}
</style></head>
<body class="{% var1 %}">
<div class="{{ var2 }}">
</div>
</body>
</html>
`,
{
backend: [
{
heads: "{{",
tails: "}}",
},
{
heads: "{%",
tails: "%}",
},
],
}
).result;
console.log("res =\n" + res);
In templating languages, it’s also possible to have IF-ELSE clauses. For example, in Nunjucks, you can have:
<td class="db{% if module_on || oodles %}on{% else %}off{% endif %} pt10"></td>
db
and pt10
are normal CSS class names, but everything else between {%
and %}
is Nunjucks code.
Now, in those cases, “lift the logic up”, pull it outside the HTML tag:
{% set switch = 'off' %}
{% if module_on || oodles %}
{% set switch = 'on' %}
{% endif %}
<td class="db {{ switch }} pt10"></td>
Now, we’re back on track, we can use opts.backend
and ignore {{
and }}
pairs.
Tapping the stream in Gulp
In Gulp, everything flows as vinyl Buffer streams. You could tap the stream, convert it to string
, perform the operations (like remove unused CSS), then convert it back to Buffer and place the stream back. We wanted to come up with a visual analogy example using waste pipes but thought we’d rather won’t.
Code-wise, here’s the idea:
import tap from "gulp-tap";
import { comb } from "email-comb";
import util from "gulp-util";
const whitelist = [
".External*",
".ReadMsgBody",
".yshortcuts",
".Mso*",
"#outlook",
".module*",
];
gulp.task("build", () => {
return gulp.src("emails/*.html").pipe(
tap((file) => {
const cleanedHtmlResult = comb(file.contents.toString(), {
whitelist,
});
util.log(
util.colors.green(
`\nremoved ${
cleanedHtmlResult.deletedFromHead.length
} from head: ${cleanedHtmlResult.deletedFromHead.join(" ")}`
)
);
util.log(
util.colors.green(
`\nremoved ${
cleanedHtmlResult.deletedFromBody.length
} from body: ${cleanedHtmlResult.deletedFromBody.join(" ")}`
)
);
file.contents = Buffer.from(cleanedHtmlResult.result);
})
);
});