condenser 1.2 → 1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/condenser/asset.rb +19 -16
- data/lib/condenser/build_cache.rb +1 -1
- data/lib/condenser/context.rb +9 -25
- data/lib/condenser/helpers/parse_helpers.rb +1 -1
- data/lib/condenser/manifest.rb +3 -1
- data/lib/condenser/pipeline.rb +8 -3
- data/lib/condenser/processors/babel_processor.rb +1 -1
- data/lib/condenser/processors/css_media_combiner_processor.rb +81 -0
- data/lib/condenser/processors/js_analyzer.rb +0 -2
- data/lib/condenser/processors/purgecss_processor.rb +6 -4
- data/lib/condenser/processors/rollup_processor.rb +37 -35
- data/lib/condenser/resolve.rb +1 -3
- data/lib/condenser/templating_engine/ejs.rb +1 -1
- data/lib/condenser/transformers/dart_sass_transformer.rb +285 -0
- data/lib/condenser/transformers/jst_transformer.rb +67 -17
- data/lib/condenser/transformers/sass/functions.rb +133 -0
- data/lib/condenser/transformers/sass/importer.rb +48 -0
- data/lib/condenser/transformers/sass.rb +4 -0
- data/lib/condenser/transformers/sass_transformer.rb +124 -281
- data/lib/condenser/transformers/svg_transformer/base.rb +26 -0
- data/lib/condenser/transformers/svg_transformer/tag.rb +54 -0
- data/lib/condenser/transformers/svg_transformer/template.rb +151 -0
- data/lib/condenser/transformers/svg_transformer/template_error.rb +2 -0
- data/lib/condenser/transformers/svg_transformer/value.rb +13 -0
- data/lib/condenser/transformers/svg_transformer/var_generator.rb +10 -0
- data/lib/condenser/transformers/svg_transformer.rb +19 -0
- data/lib/condenser/version.rb +1 -1
- data/lib/condenser.rb +17 -5
- data/test/cache_test.rb +46 -2
- data/test/dependency_test.rb +2 -2
- data/test/manifest_test.rb +34 -0
- data/test/minifiers/terser_minifier_test.rb +0 -1
- data/test/minifiers/uglify_minifier_test.rb +0 -1
- data/test/postprocessors/css_media_combiner_test.rb +107 -0
- data/test/postprocessors/purgecss_test.rb +62 -0
- data/test/preprocessor/babel_test.rb +693 -299
- data/test/preprocessor/js_analyzer_test.rb +0 -2
- data/test/processors/rollup_test.rb +50 -20
- data/test/resolve_test.rb +8 -9
- data/test/server_test.rb +6 -1
- data/test/templates/ejs_test.rb +2 -11
- data/test/templates/erb_test.rb +0 -5
- data/test/test_helper.rb +3 -1
- data/test/transformers/dart_scss_test.rb +139 -0
- data/test/transformers/jst_test.rb +165 -21
- data/test/transformers/scss_test.rb +14 -0
- data/test/transformers/svg_test.rb +40 -0
- metadata +23 -6
- data/lib/condenser/transformers/sass_transformer/importer.rb +0 -50
@@ -0,0 +1,285 @@
|
|
1
|
+
class Condenser::DartSassTransformer < Condenser::NodeProcessor
|
2
|
+
|
3
|
+
@@helper_methods = Set.new
|
4
|
+
|
5
|
+
ACCEPT = ['text/css', 'text/scss', 'text/sass']
|
6
|
+
|
7
|
+
attr_accessor :options
|
8
|
+
|
9
|
+
# Use this function to append Modules that contain functions to expose
|
10
|
+
# to dart-sass
|
11
|
+
def self.add_helper_methods(module_to_add = nil, &block)
|
12
|
+
old_methods = self.instance_methods
|
13
|
+
|
14
|
+
self.include(module_to_add) if module_to_add
|
15
|
+
self.class_eval(&block) if block_given?
|
16
|
+
|
17
|
+
@@helper_methods.merge(self.instance_methods - old_methods)
|
18
|
+
end
|
19
|
+
|
20
|
+
add_helper_methods(Condenser::Sass::Functions)
|
21
|
+
|
22
|
+
def self.syntax
|
23
|
+
:sass
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(dir, options = {})
|
27
|
+
super(dir)
|
28
|
+
npm_install('sass')
|
29
|
+
|
30
|
+
@options = options.merge({
|
31
|
+
indentedSyntax: self.class.syntax == :sass
|
32
|
+
}).freeze
|
33
|
+
end
|
34
|
+
|
35
|
+
def helper_method_signatures
|
36
|
+
x = @@helper_methods.map do |name|
|
37
|
+
arity = self.method(name).arity
|
38
|
+
signature = []
|
39
|
+
types = []
|
40
|
+
if respond_to?(:"#{name}_signature")
|
41
|
+
send(:"#{name}_signature").each do |arg, type|
|
42
|
+
signature << arg
|
43
|
+
types << type
|
44
|
+
end
|
45
|
+
elsif arity >= 0
|
46
|
+
arity.times.with_index do |a|
|
47
|
+
signature << "$arg#{a}"
|
48
|
+
types << 'String'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
["#{name}(#{signature.join(', ')})", types]
|
53
|
+
end
|
54
|
+
x
|
55
|
+
end
|
56
|
+
|
57
|
+
def call(environment, input)
|
58
|
+
@context = environment.new_context_class#.new(environment)
|
59
|
+
|
60
|
+
options = {
|
61
|
+
verbose: true,
|
62
|
+
file: File.join('/', input[:filename])
|
63
|
+
}.merge(@options)
|
64
|
+
@environment = environment
|
65
|
+
@input = input
|
66
|
+
|
67
|
+
result = exec_runtime(<<-JS)
|
68
|
+
const fs = require('fs');
|
69
|
+
const sass = require("#{npm_module_path('sass')}");
|
70
|
+
const stdin = process.stdin;
|
71
|
+
stdin.resume();
|
72
|
+
stdin.setEncoding('utf8');
|
73
|
+
|
74
|
+
// SET STDOUT to sync so that we don't get in an infinte looping waiting
|
75
|
+
// for Ruby to response when we haven't even sent the entire request.
|
76
|
+
if (process.stdout._handle) process.stdout._handle.setBlocking(true)
|
77
|
+
|
78
|
+
const source = #{JSON.generate(input[:source])};
|
79
|
+
const options = #{JSON.generate(options)};
|
80
|
+
|
81
|
+
var rid = 0;
|
82
|
+
function request(method, ...args) {
|
83
|
+
var trid = rid;
|
84
|
+
rid += 1;
|
85
|
+
console.log(JSON.stringify({ rid: trid, method: method, args: args }) + "\\n");
|
86
|
+
|
87
|
+
|
88
|
+
var readBuffer = '';
|
89
|
+
var response = null;
|
90
|
+
let chunk = new Buffer(1024);
|
91
|
+
let bytesRead;
|
92
|
+
|
93
|
+
while (response === null) {
|
94
|
+
try {
|
95
|
+
bytesRead = fs.readSync(stdin.fd, chunk, 0, 1024);
|
96
|
+
if (bytesRead === null) { exit(1); }
|
97
|
+
readBuffer += chunk.toString('utf8', 0, bytesRead);
|
98
|
+
[readBuffer, response] = readResponse(readBuffer);
|
99
|
+
} catch (e) {
|
100
|
+
if (e.code !== 'EAGAIN') { throw e; }
|
101
|
+
}
|
102
|
+
}
|
103
|
+
return response['return'];
|
104
|
+
}
|
105
|
+
|
106
|
+
function readResponse(buffer) {
|
107
|
+
try {
|
108
|
+
var message = JSON.parse(buffer);
|
109
|
+
return ['', message];
|
110
|
+
} catch(e) {
|
111
|
+
if (e.name === "SyntaxError") {
|
112
|
+
if (e.message.startsWith('Unexpected non-whitespace character after JSON at position ')) {
|
113
|
+
let pos = parseInt(e.message.slice(59));
|
114
|
+
let [b, r] = readResponse(buffer.slice(0,pos));
|
115
|
+
return [b + buffer.slice(pos), r];
|
116
|
+
} else if (e.message.startsWith('Unexpected token { in JSON at position ')) {
|
117
|
+
// This can be removed, once dropping support for node <= v18
|
118
|
+
var pos = parseInt(e.message.slice(39));
|
119
|
+
let [b, r] = readResponse(buffer.slice(0,pos));
|
120
|
+
return [b + buffer.slice(pos), r];
|
121
|
+
} else {
|
122
|
+
return [buffer, null];
|
123
|
+
}
|
124
|
+
} else {
|
125
|
+
console.log(JSON.stringify({method: 'error', args: [e.name, e.message]}) + "\\n");
|
126
|
+
process.exit(1);
|
127
|
+
}
|
128
|
+
}
|
129
|
+
}
|
130
|
+
|
131
|
+
|
132
|
+
options.importer = function(url, prev) { return request('load', url, prev); };
|
133
|
+
|
134
|
+
const call_fn = function(name, types, args) {
|
135
|
+
const transformedArgs = [];
|
136
|
+
request('log', '==================')
|
137
|
+
args.forEach((a, i) => {
|
138
|
+
if (types[i] === 'Map') {
|
139
|
+
// Don't know how to go from SassMap to hash yet
|
140
|
+
transformedArgs.push({});
|
141
|
+
} else if (types[i] === 'List') {
|
142
|
+
// Don't know how to go from SassList to hash yet
|
143
|
+
transformedArgs.push([]);
|
144
|
+
} else if (!(a instanceof sass.types[types[i]])) {
|
145
|
+
throw "$url: Expected a string.";
|
146
|
+
} else {
|
147
|
+
transformedArgs.push(a.getValue());
|
148
|
+
}
|
149
|
+
|
150
|
+
|
151
|
+
// if (types[i] === 'List') { a = a.contents(); }
|
152
|
+
});
|
153
|
+
request('log', name, transformedArgs, types)
|
154
|
+
return new sass.types.String(request('call', name, transformedArgs));
|
155
|
+
}
|
156
|
+
options.functions = {};
|
157
|
+
#{JSON.generate(helper_method_signatures)}.forEach( (f) => {
|
158
|
+
let name = f[0].replace(/-/g, '_').replace(/(^[^\\(]+)\\(.*\\)$/, '$1');
|
159
|
+
options.functions[f[0]] = (...a) => call_fn(name, f[1], a);
|
160
|
+
})
|
161
|
+
|
162
|
+
try {
|
163
|
+
options.data = source;
|
164
|
+
const result = sass.renderSync(options);
|
165
|
+
request('result', result.css.toString());
|
166
|
+
process.exit(0);
|
167
|
+
} catch(e) {
|
168
|
+
request('error', e.name, e.message);
|
169
|
+
process.exit(1);
|
170
|
+
}
|
171
|
+
JS
|
172
|
+
|
173
|
+
input[:source] = result
|
174
|
+
# input[:map] = map.to_json({})
|
175
|
+
input[:linked_assets] += @context.links
|
176
|
+
input[:process_dependencies] += @context.dependencies
|
177
|
+
end
|
178
|
+
|
179
|
+
def find(importee, importer = nil)
|
180
|
+
# importer ||= @input[:source_file]
|
181
|
+
@environment.find(expand_path(importee, importer), nil, accept: ACCEPT)
|
182
|
+
end
|
183
|
+
|
184
|
+
def resolve(importee, importer = nil)
|
185
|
+
# importer ||= @input[:source_file]
|
186
|
+
@environment.resolve(expand_path(importee, importer), accept: ACCEPT)
|
187
|
+
end
|
188
|
+
|
189
|
+
def expand_path(path, base=nil)
|
190
|
+
if path.start_with?('.')
|
191
|
+
File.expand_path(path, File.dirname(base)).delete_prefix(File.expand_path('.') + '/')
|
192
|
+
else
|
193
|
+
File.expand_path(path).delete_prefix(File.expand_path('.') + '/')
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def exec_runtime(script)
|
198
|
+
io = IO.popen([binary, '-e', script], 'r+')
|
199
|
+
buffer = ''
|
200
|
+
result = nil
|
201
|
+
|
202
|
+
begin
|
203
|
+
while IO.select([io]) && io_read = io.read_nonblock(1_024)
|
204
|
+
buffer << io_read
|
205
|
+
messages = buffer.split("\n\n")
|
206
|
+
buffer = buffer.end_with?("\n\n") ? '' : messages.pop
|
207
|
+
|
208
|
+
messages.each do |message|
|
209
|
+
message = JSON.parse(message)
|
210
|
+
|
211
|
+
ret = case message['method']
|
212
|
+
when 'result'
|
213
|
+
result = message['args'][0]
|
214
|
+
nil
|
215
|
+
when 'load'
|
216
|
+
importee = message['args'][0]
|
217
|
+
importer = message['args'][1]
|
218
|
+
|
219
|
+
if importee.end_with?('*')
|
220
|
+
@context.depend_on(importee)
|
221
|
+
code = ""
|
222
|
+
resolve(importee, importer).each do |f, i|
|
223
|
+
code << "@import '#{f.filename}';\n"
|
224
|
+
end
|
225
|
+
{ contents: code, map: nil }
|
226
|
+
else
|
227
|
+
if asset = find(importee)
|
228
|
+
@context.depend_on(asset.filename)
|
229
|
+
{ contents: asset.source, map: asset.sourcemap }
|
230
|
+
else
|
231
|
+
@context.depend_on(importee)
|
232
|
+
nil
|
233
|
+
end
|
234
|
+
end
|
235
|
+
when 'error'
|
236
|
+
io.write(JSON.generate({rid: message['rid'], return: nil}))
|
237
|
+
|
238
|
+
case message['args'][0]
|
239
|
+
when 'AssetNotFound'
|
240
|
+
error_message = "Could not find import \"#{message['args'][1]}\" for \"#{message['args'][2]}\".\n\n"
|
241
|
+
error_message << build_tree(message['args'][3], input, message['args'][2])
|
242
|
+
raise exec_runtime_error(error_message)
|
243
|
+
else
|
244
|
+
raise exec_runtime_error(message['args'][0] + ': ' + message['args'][1])
|
245
|
+
end
|
246
|
+
when 'call'
|
247
|
+
if respond_to?(message['args'][0])
|
248
|
+
send(message['args'][0], *message['args'][1])
|
249
|
+
else
|
250
|
+
puts '!!!'
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
io.write(JSON.generate({rid: message['rid'], return: ret}))
|
255
|
+
end
|
256
|
+
end
|
257
|
+
rescue Errno::EPIPE, EOFError
|
258
|
+
end
|
259
|
+
|
260
|
+
io.close
|
261
|
+
if $?.success?
|
262
|
+
result
|
263
|
+
else
|
264
|
+
raise exec_runtime_error(buffer)
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
protected
|
269
|
+
|
270
|
+
def condenser_context
|
271
|
+
@context
|
272
|
+
end
|
273
|
+
|
274
|
+
def condenser_environment
|
275
|
+
@environment
|
276
|
+
end
|
277
|
+
|
278
|
+
|
279
|
+
end
|
280
|
+
|
281
|
+
class Condenser::DartScssTransformer < Condenser::DartSassTransformer
|
282
|
+
def self.syntax
|
283
|
+
:scss
|
284
|
+
end
|
285
|
+
end
|
@@ -23,32 +23,32 @@ class Condenser::JstTransformer < Condenser::NodeProcessor
|
|
23
23
|
const source = #{JSON.generate(input[:source])};
|
24
24
|
const options = #{JSON.generate(opts).gsub(/"@?babel[\/-][^"]+"/) { |m| "require(#{m})"}};
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
return true;
|
29
|
-
} else if (scope.parent === null || scope.parent === undefined) {
|
30
|
-
return false;
|
31
|
-
} else {
|
32
|
-
return globalVar(scope.parent, name);
|
33
|
-
}
|
34
|
-
}
|
35
|
-
|
26
|
+
let scope = [['document', 'window']];
|
27
|
+
|
36
28
|
options['plugins'].unshift(function({ types: t }) {
|
37
29
|
return {
|
38
30
|
visitor: {
|
39
31
|
Identifier(path, state) {
|
40
|
-
if ( path.parent.type
|
32
|
+
if ( path.parent.type === 'MemberExpression' && path.parent.object !== path.node) {
|
41
33
|
return;
|
42
34
|
}
|
43
|
-
|
35
|
+
|
36
|
+
if ( path.parent.type === 'ImportSpecifier' ||
|
37
|
+
path.parent.type === 'ImportDefaultSpecifier' ||
|
38
|
+
path.parent.type === 'FunctionDeclaration' ||
|
39
|
+
path.parent.type === 'FunctionExpression' ||
|
40
|
+
path.parent.type === 'ArrowFunctionExpression' ||
|
41
|
+
path.parent.type === 'SpreadElement' ||
|
42
|
+
path.parent.type === 'CatchClause' ) {
|
43
|
+
return;
|
44
|
+
}
|
45
|
+
|
46
|
+
if ( path.parent.type === 'ObjectProperty' && path.parent.key === path.node ) {
|
44
47
|
return;
|
45
48
|
}
|
46
49
|
|
47
|
-
if (
|
48
|
-
|
49
|
-
path.node.name !== 'window' &&
|
50
|
-
!(path.node.name in global) &&
|
51
|
-
globalVar(path.scope, path.node.name)
|
50
|
+
if ( !(path.node.name in global) &&
|
51
|
+
!scope.find((s) => s.find(v => v === path.node.name))
|
52
52
|
) {
|
53
53
|
path.replaceWith(
|
54
54
|
t.memberExpression(t.identifier("locals"), path.node)
|
@@ -58,6 +58,56 @@ class Condenser::JstTransformer < Condenser::NodeProcessor
|
|
58
58
|
}
|
59
59
|
};
|
60
60
|
});
|
61
|
+
|
62
|
+
|
63
|
+
options['plugins'].unshift(function({ types: t }) {
|
64
|
+
return {
|
65
|
+
visitor: {
|
66
|
+
"FunctionDeclaration|FunctionExpression|ArrowFunctionExpression": {
|
67
|
+
enter(path, state) {
|
68
|
+
if (path.node.id) { scope[scope.length-1].push(path.node.id.name); }
|
69
|
+
scope.push(path.node.params.map((n) => n.type === 'RestElement' ? n.argument.name : n.name));
|
70
|
+
}
|
71
|
+
},
|
72
|
+
CatchClause: {
|
73
|
+
enter(path, state) {
|
74
|
+
scope.push([]);
|
75
|
+
if (path.node.param.name) { scope[scope.length-1].push(path.node.param.name); }
|
76
|
+
}
|
77
|
+
},
|
78
|
+
Scopable: {
|
79
|
+
enter(path, state) {
|
80
|
+
if (path.node.type !== 'Program' &&
|
81
|
+
path.node.type !== 'CatchClause' &&
|
82
|
+
path.parent.type !== 'FunctionDeclaration' &&
|
83
|
+
path.parent.type !== 'FunctionExpression' &&
|
84
|
+
path.parent.type !== 'ArrowFunctionExpression' &&
|
85
|
+
path.parent.type !== 'ExportDefaultDeclaration') {
|
86
|
+
scope.push([]);
|
87
|
+
}
|
88
|
+
},
|
89
|
+
exit(path, state) {
|
90
|
+
if (path.node.type !== 'Program' &&
|
91
|
+
path.parent.type !== 'ExportDefaultDeclaration') {
|
92
|
+
scope.pop();
|
93
|
+
}
|
94
|
+
}
|
95
|
+
},
|
96
|
+
ImportDeclaration(path, state) {
|
97
|
+
path.node.specifiers.forEach((s) => scope[scope.length-1].push(s.local.name));
|
98
|
+
},
|
99
|
+
ClassDeclaration(path, state) {
|
100
|
+
if (path.node.id) {
|
101
|
+
scope[scope.length-1].push(path.node.id.name)
|
102
|
+
}
|
103
|
+
},
|
104
|
+
VariableDeclaration(path, state) {
|
105
|
+
path.node.declarations.forEach((s) => scope[scope.length-1].push(s.id.name));
|
106
|
+
}
|
107
|
+
}
|
108
|
+
};
|
109
|
+
});
|
110
|
+
|
61
111
|
|
62
112
|
try {
|
63
113
|
const result = babel.transform(source, options);
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# Public: Functions injected into Sass context during Condenser evaluation.
|
2
|
+
#
|
3
|
+
# This module may be extended to add global functionality to all Condenser
|
4
|
+
# Sass environments. Though, scoping your functions to just your environment
|
5
|
+
# is preferred.
|
6
|
+
#
|
7
|
+
# module Condenser::SassProcessor::Functions
|
8
|
+
# def asset_path(path, options = {})
|
9
|
+
# end
|
10
|
+
# end
|
11
|
+
module Condenser::Sass
|
12
|
+
module Functions
|
13
|
+
|
14
|
+
# Public: Generate a url for asset path.
|
15
|
+
#
|
16
|
+
# Defaults to Context#asset_path.
|
17
|
+
def asset_path(path, options = {})
|
18
|
+
condenser_context.link_asset(path)
|
19
|
+
|
20
|
+
path = condenser_context.asset_path(path, options)
|
21
|
+
query = "?#{query}" if query
|
22
|
+
fragment = "##{fragment}" if fragment
|
23
|
+
"#{path}#{query}#{fragment}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def asset_path_signature
|
27
|
+
{
|
28
|
+
"$path" => "String",
|
29
|
+
"$options: ()" => 'Map'
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
# Public: Generate a asset url() link.
|
34
|
+
#
|
35
|
+
# path - String
|
36
|
+
def asset_url(path, options = {})
|
37
|
+
"url(#{asset_path(path, options)})"
|
38
|
+
end
|
39
|
+
|
40
|
+
def asset_url_signature
|
41
|
+
{
|
42
|
+
"$path" => "String",
|
43
|
+
"$options: ()" => 'Map'
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
# Public: Generate url for image path.
|
48
|
+
def image_path(path)
|
49
|
+
asset_path(path, type: :image)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Public: Generate a image url() link.
|
53
|
+
def image_url(path)
|
54
|
+
asset_url(path, type: :image)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Public: Generate url for video path.
|
58
|
+
def video_path(path)
|
59
|
+
asset_path(path, type: :video)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Public: Generate a video url() link.
|
63
|
+
def video_url(path)
|
64
|
+
asset_url(path, type: :video)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Public: Generate url for audio path.
|
68
|
+
def audio_path(path)
|
69
|
+
asset_path(path, type: :audio)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Public: Generate a audio url() link.
|
73
|
+
def audio_url(path)
|
74
|
+
asset_url(path, type: :audio)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Public: Generate url for font path.
|
78
|
+
def font_path(path)
|
79
|
+
asset_path(path, type: :font)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Public: Generate a font url() link.
|
83
|
+
def font_url(path)
|
84
|
+
asset_url(path, type: :font)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Public: Generate url for javascript path.
|
88
|
+
def javascript_path(path)
|
89
|
+
asset_path(path, type: :javascript)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Public: Generate a javascript url() link.
|
93
|
+
def javascript_url(path)
|
94
|
+
asset_url(path, type: :javascript)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Public: Generate url for stylesheet path.
|
98
|
+
def stylesheet_path(path)
|
99
|
+
asset_path(path, type: :stylesheet)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Public: Generate a stylesheet url() link.
|
103
|
+
def stylesheet_url(path)
|
104
|
+
asset_url(path, type: :stylesheet)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Public: Generate a data URI for asset path.
|
108
|
+
def asset_data_url(path)
|
109
|
+
url = condenser_environment.asset_data_uri(path.value)
|
110
|
+
Sass::Script::String.new("url(" + url + ")")
|
111
|
+
end
|
112
|
+
|
113
|
+
protected
|
114
|
+
# Public: The Environment.
|
115
|
+
#
|
116
|
+
# Returns Condenser::Environment.
|
117
|
+
def condenser_context
|
118
|
+
options[:condenser][:context]
|
119
|
+
end
|
120
|
+
|
121
|
+
def condenser_environment
|
122
|
+
options[:condenser][:environment]
|
123
|
+
end
|
124
|
+
|
125
|
+
# Public: Mutatable set of dependencies.
|
126
|
+
#
|
127
|
+
# Returns a Set.
|
128
|
+
def condenser_dependencies
|
129
|
+
options[:asset][:process_dependencies]
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require "sassc"
|
2
|
+
|
3
|
+
class Condenser::Sass::Importer < SassC::Importer
|
4
|
+
|
5
|
+
def imports(name, base)
|
6
|
+
name = expand_path(name, base)
|
7
|
+
env = options[:condenser][:environment]
|
8
|
+
accept = extensions.keys.map { |x| options[:condenser][:environment].extensions[x] }
|
9
|
+
|
10
|
+
options[:asset][:process_dependencies] << [name, accept.map{ |i| [i] }]
|
11
|
+
|
12
|
+
imports = []
|
13
|
+
env.resolve(name, accept: accept).sort_by(&:filename).each do |asset|
|
14
|
+
next if asset.filename == options[:filename]
|
15
|
+
imports << Import.new(asset.filename, source: asset.source, source_map_path: nil)
|
16
|
+
end
|
17
|
+
|
18
|
+
if imports.empty? && env.npm_path
|
19
|
+
package = File.join(env.npm_path, name, 'package.json')
|
20
|
+
if File.exist?(package)
|
21
|
+
package = JSON.parse(File.read(package))
|
22
|
+
if package['style']
|
23
|
+
imports << Import.new(name, source: File.read(File.join(env.npm_path, name, package['style'])), source_map_path: nil)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
raise Condenser::FileNotFound, "couldn't find file '#{name}'" if imports.empty?
|
29
|
+
|
30
|
+
imports
|
31
|
+
end
|
32
|
+
|
33
|
+
# Allow .css files to be @imported
|
34
|
+
def extensions
|
35
|
+
{ '.sass' => :sass, '.scss' => :scss, '.css' => :scss }
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def expand_path(path, base=nil)
|
41
|
+
if path.start_with?('.')
|
42
|
+
File.expand_path(path, File.dirname(base)).delete_prefix(File.expand_path('.') + '/')
|
43
|
+
else
|
44
|
+
File.expand_path(path).delete_prefix(File.expand_path('.') + '/')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|