docs: function processString(content = '', options = defaultOptions) {
  
      const comments = []
      const preservedTokens = []
  
      let pattern
  
      content = extractDataUrls(content, preservedTokens)
      content = convertRelativeUrls(content, options, preservedTokens)
      content = collectComments(content, comments)
  
      // preserve strings so their content doesn't get accidentally minified
      pattern = /("([^\\"]|\\.|\\)*")|('([^\\']|\\.|\\)*')/g
      content = content.replace(pattern, token => {
          const quote = token.substring(0, 1)
          token = token.slice(1, -1)
          // maybe the string contains a comment-like substring or more? put'em back then
          if (token.indexOf('___PRESERVE_CANDIDATE_COMMENT_') >= 0) {
              for (let i = 0, len = comments.length; i < len; i += 1) {
                  token = token.replace('___PRESERVE_CANDIDATE_COMMENT_' + i + '___', comments[i])
              }
          }
          // minify alpha opacity in filter strings
          token = token.replace(/progid:DXImageTransform.Microsoft.Alpha\(Opacity=/gi, 'alpha(opacity=')
          preservedTokens.push(token)
          return quote + ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + '___' + quote
      })
  
      // strings are safe, now wrestle the comments
      for (let i = 0, len = comments.length; i < len; i += 1) {
  
          let token = comments[i]
          let placeholder = '___PRESERVE_CANDIDATE_COMMENT_' + i + '___'
  
          // ! in the first position of the comment means preserve
          // so push to the preserved tokens keeping the !
          if (token.charAt(0) === '!') {
              if (options.cuteComments) {
                  preservedTokens.push(token.substring(1).replace(/\r\n/g, '\n'))
              } else if (options.uglyComments) {
                  preservedTokens.push(token.substring(1).replace(/[\r\n]/g, ''))
              } else {
                  preservedTokens.push(token)
              }
              content = content.replace(placeholder, ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + '___')
              continue
          }
  
          // \ in the last position looks like hack for Mac/IE5
          // shorten that to /*\*/ and the next one to /**/
          if (token.charAt(token.length - 1) === '\\') {
              preservedTokens.push('\\')
              content = content.replace(placeholder, ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + '___')
              i = i + 1 // attn: advancing the loop
              preservedTokens.push('')
              content = content.replace(
                  '___PRESERVE_CANDIDATE_COMMENT_' + i + '___',
                  ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + '___'
              )
              continue
          }
  
          // keep empty comments after child selectors (IE7 hack)
          // e.g. html >/**/ body
          if (token.length === 0) {
              let startIndex = content.indexOf(placeholder)
              if (startIndex > 2) {
                  if (content.charAt(startIndex - 3) === '>') {
                      preservedTokens.push('')
                      content = content.replace(placeholder, ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + '___')
                  }
              }
          }
  
          // in all other cases kill the comment
          content = content.replace(`/*${placeholder}*/`, '')
      }
  
      // parse simple @variables blocks and remove them
      if (options.expandVars) {
          const vars = {}
          pattern = /@variables\s*\{\s*([^\}]+)\s*\}/g
          content = content.replace(pattern, (_, f1) => {
              pattern = /\s*([a-z0-9\-]+)\s*:\s*([^;\}]+)\s*/gi
              f1.replace(pattern, (_, f1, f2) => {
                  if (f1 && f2) {
                      vars[f1] = f2
                  }
                  return ''
              })
              return ''
          })
  
          // replace var(x) with the value of x
          pattern = /var\s*\(\s*([^\)]+)\s*\)/g
          content = content.replace(pattern, (_, f1) => {
              return vars[f1] || 'none'
          })
      }
  
      // normalize all whitespace strings to single spaces. Easier to work with that way.
      content = content.replace(/\s+/g, ' ')
  
      // preserve formulas in calc() before removing spaces
      pattern = /calc\(([^;}]*)\)/g
      content = content.replace(pattern, (_, f1) => {
          preservedTokens.push(
              'calc(' +
              f1.replace(/(^\s*|\s*$)/g, '')
                  .replace(/\( /g, '(')
                  .replace(/ \)/g, ')') +
              ')'
          )
          return ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + '___'
      })
  
      // preserve matrix
      pattern = /\s*filter:\s*progid:DXImageTransform.Microsoft.Matrix\(([^\)]+)\);/g
      content = content.replace(pattern, (_, f1) => {
          preservedTokens.push(f1)
          return 'filter:progid:DXImageTransform.Microsoft.Matrix(' + ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + '___);'
      })
  
      // remove the spaces before the things that should not have spaces before them.
      // but, be careful not to turn 'p :link {...}' into 'p:link{...}'
      // swap out any pseudo-class colons with the token, and then swap back.
      pattern = /(^|\})(([^\{:])+:)+([^\{]*\{)/g
      content = content.replace(pattern, token => token.replace(/:/g, '___PSEUDOCLASSCOLON___'))
  
      // remove spaces before the things that should not have spaces before them.
      content = content.replace(/\s+([!{};:>+\(\)\],])/g, '$1')
  
      // restore spaces for !important
      content = content.replace(/!important/g, ' !important')
  
      // bring back the colon
      content = content.replace(/___PSEUDOCLASSCOLON___/g, ':')
  
      // preserve 0 followed by a time unit for properties using time units
      pattern = /\s*(animation|animation-delay|animation-duration|transition|transition-delay|transition-duration):\s*([^;}]+)/gi
      content = content.replace(pattern, (_, f1, f2) => {
  
          f2 = f2.replace(/(^|\D)0?\.?0(m?s)/gi, (_, g1, g2) => {
              preservedTokens.push('0' + g2)
              return g1 + ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + '___'
          })
  
          return f1 + ':' + f2
      })
  
      // preserve unit for flex-basis within flex and flex-basis (ie10 bug)
      pattern = /\s*(flex|flex-basis):\s*([^;}]+)/gi
      content = content.replace(pattern, (_, f1, f2) => {
          let f2b = f2.split(/\s+/)
          preservedTokens.push(f2b.pop())
          f2b.push(___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + '___')
          f2b = f2b.join(' ')
          return `${f1}:${f2b}`
      })
  
      // preserve 0% in hsl and hsla color definitions
      content = content.replace(/(hsla?)\(([^)]+)\)/g, (_, f1, f2) => {
          var f0 = []
          f2.split(',').forEach(part => {
              part = part.replace(/(^\s+|\s+$)/g, '')
              if (part === '0%') {
                  preservedTokens.push('0%')
                  f0.push(___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + '___')
              } else {
                  f0.push(part)
              }
          })
          return f1 + '(' + f0.join(',') + ')'
      })
  
      // preserve 0 followed by unit in keyframes steps (WIP)
      content = keyframes(content, preservedTokens)
  
      // retain space for special IE6 cases
      content = content.replace(/:first-(line|letter)(\{|,)/gi, (_, f1, f2) => ':first-' + f1.toLowerCase() + ' ' + f2)
  
      // newlines before and after the end of a preserved comment
      if (options.cuteComments) {
          content = content.replace(/\s*\/\*/g, '___PRESERVED_NEWLINE___/*')
          content = content.replace(/\*\/\s*/g, '*/___PRESERVED_NEWLINE___')
          // no space after the end of a preserved comment
      } else {
          content = content.replace(/\*\/\s*/g, '*/')
      }
  
      // If there are multiple @charset directives, push them to the top of the file.
      pattern = /^(.*)(@charset)( "[^"]*";)/gi
      content = content.replace(pattern, (_, f1, f2, f3) => f2.toLowerCase() + f3 + f1)
  
      // When all @charset are at the top, remove the second and after (as they are completely ignored).
      pattern = /^((\s*)(@charset)( [^;]+;\s*))+/gi
      content = content.replace(pattern, (_, __, f2, f3, f4) => f2 + f3.toLowerCase() + f4)
  
      // lowercase some popular @directives (@charset is done right above)
      pattern = /@(font-face|import|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?keyframe|media|page|namespace)/gi
      content = content.replace(pattern, (_, f1) => '@' + f1.toLowerCase())
  
      // lowercase some more common pseudo-elements
      pattern = /:(active|after|before|checked|disabled|empty|enabled|first-(?:child|of-type)|focus|hover|last-(?:child|of-type)|link|only-(?:child|of-type)|root|:selection|target|visited)/gi
      content = content.replace(pattern, (_, f1) => ':' + f1.toLowerCase())
  
      // if there is a @charset, then only allow one, and push to the top of the file.
      content = content.replace(/^(.*)(@charset \"[^\"]*\";)/g, '$2$1')
      content = content.replace(/^(\s*@charset [^;]+;\s*)+/g, '$1')
  
      // lowercase some more common functions
      pattern = /:(lang|not|nth-child|nth-last-child|nth-last-of-type|nth-of-type|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?any)\(/gi
      content = content.replace(pattern, (_, f1) => ':' + f1.toLowerCase() + '(')
  
      // lower case some common function that can be values
      // NOTE: rgb() isn't useful as we replace with #hex later, as well as and() is already done for us right after this
      pattern = /([:,\( ]\s*)(attr|color-stop|from|rgba|to|url|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?(?:calc|max|min|(?:repeating-)?(?:linear|radial)-gradient)|-webkit-gradient)/gi
      content = content.replace(pattern, (_, f1, f2) => f1 + f2.toLowerCase())
  
      // put the space back in some cases, to support stuff like
      // @media screen and (-webkit-min-device-pixel-ratio:0){
      content = content.replace(/\band\(/gi, 'and (')
  
      // remove the spaces after the things that should not have spaces after them.
      content = content.replace(/([!{}:;>+\(\[,])\s+/g, '$1')
  
      // remove unnecessary semicolons
      content = content.replace(/;+\}/g, '}')
  
      // replace 0(px,em,%) with 0.
      content = content.replace(/(^|[^.0-9\\])(?:0?\.)?0(?:ex|ch|r?em|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|g?rad|turn|m?s|k?Hz|dpi|dpcm|dppx|%)/gi, '$10')
  
      // Replace x.0(px,em,%) with x(px,em,%).
      content = content.replace(/([0-9])\.0(ex|ch|r?em|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|g?rad|turn|m?s|k?Hz|dpi|dpcm|dppx|%| |;)/gi, '$1$2')
  
      // replace 0 0 0 0; with 0.
      content = content.replace(/:0 0 0 0(;|\})/g, ':0$1')
      content = content.replace(/:0 0 0(;|\})/g, ':0$1')
      content = content.replace(/:0 0(;|\})/g, ':0$1')
  
      // replace background-position:0; with background-position:0 0;
      // same for transform-origin and box-shadow
      pattern = /(background-position|transform-origin|webkit-transform-origin|moz-transform-origin|o-transform-origin|ms-transform-origin|box-shadow):0(;|\})/gi
      content = content.replace(pattern, (_, f1, f2) => f1.toLowerCase() + ':0 0' + f2)
  
      // replace 0.6 to .6, but only when preceded by : or a white-space
      content = content.replace(/(:|\s)0+\.(\d+)/g, '$1.$2')
  
      // shorten colors from rgb(51,102,153) to #336699
      // this makes it more likely that it'll get further compressed in the next step.
      pattern = /rgb\s*\(\s*([0-9,\s]+)\s*\)/gi
      content = content.replace(pattern, (_, f1) => {
          const rgbcolors = f1.split(',')
          let hexcolor = '#'
          for (let i = 0; i < rgbcolors.length; i += 1) {
              let val = parseInt(rgbcolors[i], 10)
              if (val < 16) {
                  hexcolor += '0'
              }
              if (val > 255) {
                  val = 255
              }
              hexcolor += val.toString(16)
          }
          return hexcolor
      })
  
      // Shorten colors from #AABBCC to #ABC.
      content = compressHexColors(content)
  
      // Replace #f00 -> red
      content = content.replace(/(:|\s)(#f00)(;|})/g, '$1red$3')
  
      // Replace other short color keywords
      content = content.replace(/(:|\s)(#000080)(;|})/g, '$1navy$3')
      content = content.replace(/(:|\s)(#808080)(;|})/g, '$1gray$3')
      content = content.replace(/(:|\s)(#808000)(;|})/g, '$1olive$3')
      content = content.replace(/(:|\s)(#800080)(;|})/g, '$1purple$3')
      content = content.replace(/(:|\s)(#c0c0c0)(;|})/g, '$1silver$3')
      content = content.replace(/(:|\s)(#008080)(;|})/g, '$1teal$3')
      content = content.replace(/(:|\s)(#ffa500)(;|})/g, '$1orange$3')
      content = content.replace(/(:|\s)(#800000)(;|})/g, '$1maroon$3')
  
      // border: none -> border:0
      pattern = /(border|border-top|border-right|border-bottom|border-left|outline|background):none(;|\})/gi
      content = content.replace(pattern, (_, f1, f2) => f1.toLowerCase() + ':0' + f2)
  
      // shorter opacity IE filter
      content = content.replace(/progid:DXImageTransform\.Microsoft\.Alpha\(Opacity=/gi, 'alpha(opacity=')
  
      // Find a fraction that is used for Opera's -o-device-pixel-ratio query
      // Add token to add the '\' back in later
      content = content.replace(/\(([\-A-Za-z]+):([0-9]+)\/([0-9]+)\)/g, '($1:$2___QUERY_FRACTION___$3)')
  
      // remove empty rules.
      content = content.replace(/[^\};\{\/]+\{\}/g, '')
  
      // Add '\' back to fix Opera -o-device-pixel-ratio query
      content = content.replace(/___QUERY_FRACTION___/g, '/')
  
      // some source control tools don't like it when files containing lines longer
      // than, say 8000 characters, are checked in. The linebreak option is used in
      // that case to split long lines after a specific column.
      if (options.maxLineLen > 0) {
          const lines = []
          let line = []
          for (let i = 0, len = content.length; i < len; i += 1) {
              let ch = content.charAt(i)
              line.push(ch)
              if (ch === '}' && line.length > options.maxLineLen) {
                  lines.push(line.join(''))
                  line = []
              }
          }
          if (line.length) {
              lines.push(line.join(''))
          }
  
          content = lines.join('\n')
      }
  
      // replace multiple semi-colons in a row by a single one
      // see SF bug #1980989
      content = content.replace(/;;+/g, ';')
  
      // trim the final string (for any leading or trailing white spaces)
      content = content.replace(/(^\s*|\s*$)/g, '')
  
      // restore preserved tokens
      for (let i = preservedTokens.length - 1; i >= 0; i--) {
          content = content.replace(___PRESERVED_TOKEN_ + i + '___', preservedTokens[i], 'g')
      }
  
      // restore preserved newlines
      content = content.replace(/___PRESERVED_NEWLINE___/g, '\n')
  
      // return
      return content
  }