Repository: Automattic/juice
Pull request #304 (2018-05-30 19:03:57)

Add preserveKeyFrames option


Patch:
diff --git a/README.md b/README.md
index 15c18dd..d4a6904 100644
--- a/README.md
+++ b/README.md
@@ -69,7 +69,7 @@ All juice methods take an options object that can contain any of these propertie
 
 * `extraCss` - extra css to apply to the file. Defaults to `""`.
 
-* `insertPreservedExtraCss` - whether to insert into the document any preserved `@media` or `@font-face` content from `extraCss` when using `preserveMediaQueries` or `preserveFontFaces`. When `true` order of preference to append the `<style>` element is into `head`, then `body`, then at the end of the document. When a `string` the value is treated as a CSS/jQuery/cheerio selector, and when found, the `<style>` tag will be appended to the end of the first match. Defaults to `true`.
+* `insertPreservedExtraCss` - whether to insert into the document any preserved `@media` or `@font-face` content from `extraCss` when using `preserveMediaQueries`, `preserveFontFaces` or `preserveKeyFrames`. When `true` order of preference to append the `<style>` element is into `head`, then `body`, then at the end of the document. When a `string` the value is treated as a CSS/jQuery/cheerio selector, and when found, the `<style>` tag will be appended to the end of the first match. Defaults to `true`.
 
 * `inlinePseudoElements` - Whether to insert pseudo elements (`::before` and `::after`) as `<span>` into the DOM. *Note*: Inserting pseudo elements will modify the DOM and may conflict with CSS selectors elsewhere on the page (e.g., `:last-child`).
 
@@ -79,6 +79,8 @@ All juice methods take an options object that can contain any of these propertie
 
 * `preserveMediaQueries` - preserves all media queries (and contained styles) within `<style></style>` tags as a refinement when `removeStyleTags` is `true`. Other styles are removed. Defaults to `true`.
 
+* `preserveKeyFrames` - preserves all key frames within `<style></style>` tags as a refinement when `removeStyleTags` is `true`. Other styles are removed. Defaults to `true`.
+
 * `removeStyleTags` - whether to remove the original `<style></style>` tags after (possibly) inlining the css from them. Defaults to `true`.
 
 * `webResources` - An options object that will be passed to [web-resource-inliner](https://www.npmjs.com/package/web-resource-inliner) for juice functions that will get remote resources (`juiceResources` and `juiceFile`). Defaults to `{}`.
diff --git a/juice.d.ts b/juice.d.ts
index 6d7e268..854277b 100644
--- a/juice.d.ts
+++ b/juice.d.ts
@@ -39,6 +39,7 @@ declare namespace juice {
     removeStyleTags?: boolean;
     preserveMediaQueries?: boolean;
     preserveFontFaces?: boolean;
+    preserveKeyFrames?: boolean;
     insertPreservedExtraCss?: boolean;
     applyWidthAttributes?: boolean;
     applyHeightAttributes?: boolean;
diff --git a/lib/cli.js b/lib/cli.js
index a86f4e4..2255525 100644
--- a/lib/cli.js
+++ b/lib/cli.js
@@ -57,6 +57,10 @@ cli.options = {
     pMap: 'preserveFontFaces',
     def: 'preserve font faces?',
     coercion: JSON.parse },
+  'preserve-key-frames': {
+    pMap: 'preserveKeyFrames',
+    def: 'preserve key frames?',
+    coercion: JSON.parse },
   'apply-width-attributes': {
     pMap: 'applyWidthAttributes',
     def: 'apply width attributes to relevent elements?',
diff --git a/lib/inline.js b/lib/inline.js
index 066a989..60345e5 100644
--- a/lib/inline.js
+++ b/lib/inline.js
@@ -57,7 +57,8 @@ function inlineDocument($, css, options) {
   if (options.insertPreservedExtraCss && options.extraCss) {
     var preservedText = utils.getPreservedText(options.extraCss, {
       mediaQueries: options.preserveMediaQueries,
-      fontFaces: options.preserveFontFaces
+      fontFaces: options.preserveFontFaces,
+      keyFrames: options.preserveKeyFrames
     });
     if (preservedText) {
       var $appendTo = null;
@@ -346,7 +347,8 @@ function getStylesData($, options) {
     if (options.removeStyleTags && $(styleElement).attr('data-embed') === undefined) {
       var preservedText = utils.getPreservedText(styleElement.childNodes[0].nodeValue, {
         mediaQueries: options.preserveMediaQueries,
-        fontFaces: options.preserveFontFaces
+        fontFaces: options.preserveFontFaces,
+        keyFrames: options.preserveKeyFrames
       });
       if (preservedText) {
         styleElement.childNodes[0].nodeValue = preservedText;
diff --git a/lib/utils.js b/lib/utils.js
index 71b7972..ba4e211 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -93,7 +93,8 @@ exports.getPreservedText = function(css, options) {
 
   for (var i = rules.length - 1; i >= 0; i--) {
     if ((options.fontFaces && rules[i].type === 'font-face') ||
-        (options.mediaQueries && rules[i].type === 'media')) {
+        (options.mediaQueries && rules[i].type === 'media') ||
+        (options.keyFrames && rules[i].type === 'keyframes')) {
       preserved.unshift(
         mensch.stringify(
           { stylesheet: { rules: [ rules[i] ] }},
@@ -156,6 +157,7 @@ exports.getDefaultOptions = function(options) {
     removeStyleTags: true,
     preserveMediaQueries: true,
     preserveFontFaces: true,
+    preserveKeyFrames: true,
     applyWidthAttributes: true,
     applyHeightAttributes: true,
     applyAttributesTableElements: true,


Test patch:
diff --git a/test/cases/juice-content/keyframes-preserve.css b/test/cases/juice-content/keyframes-preserve.css
new file mode 100644
index 0000000..e69de29
diff --git a/test/cases/juice-content/keyframes-preserve.html b/test/cases/juice-content/keyframes-preserve.html
new file mode 100644
index 0000000..dc5a86d
--- /dev/null
+++ b/test/cases/juice-content/keyframes-preserve.html
@@ -0,0 +1,22 @@
+<html>
+<head>
+    <style>
+        p {
+            color: blue;
+            opacity: 0;
+            animation: keyframeName 3s infinite;
+        }
+        @keyframes keyframeName {
+            0%, 100% {
+                opacity: 0;
+            }
+            50% {
+                opacity: 1;
+            }
+        }
+    </style>
+</head>
+<body>
+    <p>hi</p>
+</body>
+</html>
diff --git a/test/cases/juice-content/keyframes-preserve.json b/test/cases/juice-content/keyframes-preserve.json
new file mode 100644
index 0000000..678f8de
--- /dev/null
+++ b/test/cases/juice-content/keyframes-preserve.json
@@ -0,0 +1,5 @@
+{
+    "url": "./",
+    "removeStyleTags": true,
+    "preserveKeyFrames": true
+}
diff --git a/test/cases/juice-content/keyframes-preserve.out b/test/cases/juice-content/keyframes-preserve.out
new file mode 100644
index 0000000..e9d4582
--- /dev/null
+++ b/test/cases/juice-content/keyframes-preserve.out
@@ -0,0 +1,19 @@
+<html>
+<head>
+    <style>
+@keyframes keyframeName {
+  0%,
+100% {
+    opacity: 0;
+  }
+
+  50% {
+    opacity: 1;
+  }
+}
+</style>
+</head>
+<body>
+    <p style="color: blue; opacity: 0; animation: keyframeName 3s infinite;">hi</p>
+</body>
+</html>
diff --git a/test/cli.js b/test/cli.js
index 7924648..6c1e44c 100644
--- a/test/cli.js
+++ b/test/cli.js
@@ -21,6 +21,7 @@ it('cli parses options', function(done) {
   assert.strictEqual(cli.argsToOptions({'preserveImportant': 'true'}).preserveImportant, true);
   assert.strictEqual(cli.argsToOptions({'preserveMediaQueries': 'true'}).preserveMediaQueries, true);
   assert.strictEqual(cli.argsToOptions({'preserveFontFaces': 'true'}).preserveFontFaces, true);
+  assert.strictEqual(cli.argsToOptions({'preserveKeyFrames': 'true'}).preserveKeyFrames, true);
   assert.strictEqual(cli.argsToOptions({'applyWidthAttributes': 'true'}).applyWidthAttributes, true);
   assert.strictEqual(cli.argsToOptions({'applyHeightAttributes': 'true'}).applyHeightAttributes, true);
   assert.strictEqual(cli.argsToOptions({'applyAttributesTableElements': 'true'}).applyAttributesTableElements, true);
diff --git a/test/typescript/juice-tests.ts b/test/typescript/juice-tests.ts
index f23c11b..13b6e85 100644
--- a/test/typescript/juice-tests.ts
+++ b/test/typescript/juice-tests.ts
@@ -15,6 +15,7 @@ const mostOptions = {
   removeStyleTags: true,
   preserveMediaQueries: true,
   preserveFontFaces: true,
+  preserveKeyFrames: true,
   insertPreservedExtraCss: true,
   applyWidthAttributes: true,
   applyHeightAttributes: true,