forked from ai/autoprefixer-rails
/
processor.rb
161 lines (134 loc) · 4.5 KB
/
processor.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# frozen_string_literal: true
require "pathname"
require "execjs"
require "json"
IS_SECTION = /^\s*\[(.+)\]\s*$/.freeze
module AutoprefixerRails
# Ruby to JS wrapper for Autoprefixer processor instance
class Processor
SUPPORTED_RUNTIMES = [ExecJS::Runtimes::Node, ExecJS::Runtimes::MiniRacer].freeze
def initialize(params = {})
@params = params || {}
end
# Process `css` and return result.
#
# Options can be:
# * `from` with input CSS file name. Will be used in error messages.
# * `to` with output CSS file name.
# * `map` with true to generate new source map or with previous map.
def process(css, opts = {})
opts = convert_options(opts)
plugin_opts = params_with_browsers(opts[:from]).merge(opts)
process_opts = {
from: plugin_opts.delete(:from),
to: plugin_opts.delete(:to),
map: plugin_opts.delete(:map)
}
begin
result = runtime.call("autoprefixer.process", css, process_opts, plugin_opts)
rescue ExecJS::ProgramError => e
contry_error = "BrowserslistError: " \
"Country statistics are not supported " \
"in client-side build of Browserslist"
if e.message == contry_error
raise "Country statistics is not supported in AutoprefixerRails. " \
"Use Autoprefixer with webpack or other Node.js builder."
else
raise e
end
end
Result.new(result["css"], result["map"], result["warnings"])
end
# Return, which browsers and prefixes will be used
def info
runtime.call("autoprefixer.info", params_with_browsers)
end
# Parse Browserslist config
def parse_config(config)
sections = { "defaults" => [] }
current = "defaults"
config.gsub(/#[^\n]*/, "")
.split(/\n/)
.map(&:strip)
.reject(&:empty?)
.each do |line|
if IS_SECTION =~ line
current = line.match(IS_SECTION)[1].strip
sections[current] ||= []
else
sections[current] << line
end
end
sections
end
private
def params_with_browsers(from = nil)
from ||= if defined?(Rails) && Rails.respond_to?(:root) && Rails.root
Rails.root.join("app/assets/stylesheets").to_s
else
"."
end
params = @params
if !params.key?(:browsers) && !params.key?(:overrideBrowserslist) && from
file = find_config(from)
if file
env = params[:env].to_s || "development"
config = parse_config(file)
params = params.dup
params[:overrideBrowserslist] = (config[env] || config["defaults"])
end
end
params
end
# Convert ruby_options to jsOptions
def convert_options(opts)
converted = {}
opts.each_pair do |name, value|
if /_/ =~ name
name = name.to_s.gsub(/_\w/) { |i| i.delete("_").upcase }.to_sym
end
value = convert_options(value) if value.is_a? Hash
converted[name] = value
end
converted
end
# Try to find Browserslist config
def find_config(file)
path = Pathname(file).expand_path
while path.parent != path
config1 = path.join("browserslist")
return config1.read if config1.exist? && !config1.directory?
config2 = path.join(".browserslistrc")
return config2.read if config2.exist? && !config1.directory?
path = path.parent
end
nil
end
# Lazy load for JS library
def runtime
@runtime ||= begin
if ExecJS.runtime == ExecJS::Runtimes::Node
version = ExecJS.runtime.eval("this.process.version")
major = version.match(/^v(\d+)/)[1].to_i
# supports 10, 12, 14+
unless [10, 12].include?(major) || major >= 14
raise "Autoprefixer doesn’t support Node #{version}. Update it."
end
end
ExecJS.compile(build_js)
rescue ExecJS::RuntimeError
raise if SUPPORTED_RUNTIMES.include?(ExecJS.runtime)
# Only complain about unsupported runtimes when it failed to parse our script.
raise <<~MSG
Your ExecJS runtime #{ExecJS.runtime.name} isn't supported by autoprefixer-rails,
please switch to #{SUPPORTED_RUNTIMES.map(&:name).join(" or ")}
MSG
end
end
def build_js
root = Pathname(File.dirname(__FILE__))
path = root.join("../../vendor/autoprefixer.js")
path.read
end
end
end