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)
-
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)
-
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 = {})
-
(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)
-
(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)
-
(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