class Roadie::Inliner

<a href=“/” style=“color:red”></a>
becomes
<a href=“/”></a> # DOM
a { color: red; } # StyleBlock
Inlining means that {StyleBlock}s and a DOM tree are combined:
The Inliner inlines stylesheets to the elements of the DOM.
@api private

def add_uninlinable_styles(parent, blocks, merge_media_queries)

Parameters:
  • merge_media_queries (Boolean) -- Whether to group media queries
  • blocks (Array) -- Non-inlineable style blocks
  • parent (Symbol) -- Where to put the styles
def add_uninlinable_styles(parent, blocks, merge_media_queries)
  return if blocks.empty?
  parent_node =
    case parent
    when :head
      find_head
    when :root
      dom
    else
      raise ArgumentError, "Parent must be either :head or :root. Was #{parent.inspect}"
    end
  create_style_element(blocks, parent_node, merge_media_queries)
end

def apply_element_style(element, builder)

def apply_element_style(element, builder)
  element["style"] = [builder.attribute_string, element["style"]].compact.join(";")
end

def apply_style_map(style_map)

def apply_style_map(style_map)
  style_map.each_element { |element, builder| apply_element_style(element, builder) }
end

def consume_stylesheets

def consume_stylesheets
  style_map = StyleMap.new
  extra_blocks = []
  each_style_block do |stylesheet, block|
    if (elements = selector_elements(stylesheet, block))
      style_map.add elements, block.properties
    else
      extra_blocks << block
    end
  end
  [style_map, extra_blocks]
end

def create_style_element(style_blocks, parent, merge_media_queries)

def create_style_element(style_blocks, parent, merge_media_queries)
  return unless parent
  element = Nokogiri::XML::Node.new('style', parent.document)
  element.content =
    if merge_media_queries
      styles_in_shared_media_queries(style_blocks).join("\n")
    else
      styles_in_individual_media_queries(style_blocks).join("\n")
    end
  parent.add_child(element)
end

def each_style_block

def each_style_block
  stylesheets.each do |stylesheet|
    stylesheet.blocks.each do |block|
      yield stylesheet, block
    end
  end
end

def elements_matching_selector(stylesheet, selector)

def elements_matching_selector(stylesheet, selector)
  dom.css(selector.to_s)
# There's no way to get a list of supported pseudo selectors, so we're left
# with having to rescue errors.
# Pseudo selectors that are known to be bad are skipped automatically but
# this will catch the rest.
rescue Nokogiri::XML::XPath::SyntaxError, Nokogiri::CSS::SyntaxError => error
  Utils.warn "Cannot inline #{selector.inspect} from \"#{stylesheet.name}\" stylesheet. If this is valid CSS, please report a bug."
  nil
rescue => error
  Utils.warn "Got error when looking for #{selector.inspect} (from \"#{stylesheet.name}\" stylesheet): #{error}"
  raise unless error.message.include?('XPath')
  nil
end

def find_head

def find_head
  dom.at_xpath('html/head')
end

def initialize(stylesheets, dom)

Parameters:
  • dom (Nokogiri::HTML::Document) --
  • stylesheets (Array) -- the stylesheets to use in the inlining
def initialize(stylesheets, dom)
  @stylesheets = stylesheets
  @dom = dom
end

def inline(options = {})

Returns:
  • (nil) -

Options Hash: (**options)
  • :merge_media_queries (true, false) --
  • :keep_uninlinable_in (:root, :head) --
  • :keep_uninlinable_css (true, false) --
def inline(options = {})
  keep_uninlinable_css = options.fetch(:keep_uninlinable_css, true)
  keep_uninlinable_in = options.fetch(:keep_uninlinable_in, :head)
  merge_media_queries = options.fetch(:merge_media_queries, true)
  style_map, extra_blocks = consume_stylesheets
  apply_style_map(style_map)
  if keep_uninlinable_css
    add_uninlinable_styles(keep_uninlinable_in, extra_blocks, merge_media_queries)
  end
  nil
end

def selector_elements(stylesheet, block)

def selector_elements(stylesheet, block)
  block.inlinable? && elements_matching_selector(stylesheet, block.selector)
end

def styles_in_individual_media_queries(style_blocks)

Returns:
  • (Array) -

Parameters:
  • style_blocks (Array) -- All style blocks
def styles_in_individual_media_queries(style_blocks)
  style_blocks.map do |css_rule|
    if css_rule.media == ['all']
      css_rule
    else
      "@media #{css_rule.media.join(', ')} {\n#{css_rule}\n}"
    end
  end
end

def styles_in_shared_media_queries(style_blocks)

Returns:
  • (Array) -

Parameters:
  • style_blocks (Array) -- Style blocks that could not be inlined
def styles_in_shared_media_queries(style_blocks)
  style_blocks.group_by(&:media).map do |media_types, blocks|
    css_rules = blocks.map(&:to_s).join("\n")
    if media_types == ['all']
      css_rules
    else
      "@media #{media_types.join(', ')} {\n#{css_rules}\n}"
    end
  end
end