condenser 0.0.1
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +1 -0
- data/lib/condenser.rb +108 -0
- data/lib/condenser/asset.rb +221 -0
- data/lib/condenser/cache/memory_store.rb +92 -0
- data/lib/condenser/cache/null_store.rb +37 -0
- data/lib/condenser/context.rb +272 -0
- data/lib/condenser/encoding_utils.rb +155 -0
- data/lib/condenser/environment.rb +50 -0
- data/lib/condenser/errors.rb +11 -0
- data/lib/condenser/export.rb +68 -0
- data/lib/condenser/manifest.rb +89 -0
- data/lib/condenser/pipeline.rb +82 -0
- data/lib/condenser/processors/babel.min.js +25 -0
- data/lib/condenser/processors/babel_processor.rb +87 -0
- data/lib/condenser/processors/node_processor.rb +38 -0
- data/lib/condenser/processors/rollup.js +24083 -0
- data/lib/condenser/processors/rollup_processor.rb +164 -0
- data/lib/condenser/processors/sass_importer.rb +81 -0
- data/lib/condenser/processors/sass_processor.rb +300 -0
- data/lib/condenser/resolve.rb +202 -0
- data/lib/condenser/server.rb +307 -0
- data/lib/condenser/templating_engine/erb.rb +21 -0
- data/lib/condenser/utils.rb +32 -0
- data/lib/condenser/version.rb +3 -0
- data/lib/condenser/writers/file_writer.rb +28 -0
- data/lib/condenser/writers/zlib_writer.rb +42 -0
- data/test/cache_test.rb +24 -0
- data/test/environment_test.rb +49 -0
- data/test/manifest_test.rb +513 -0
- data/test/pipeline_test.rb +31 -0
- data/test/preprocessor/babel_test.rb +21 -0
- data/test/processors/rollup_test.rb +71 -0
- data/test/resolve_test.rb +105 -0
- data/test/server_test.rb +361 -0
- data/test/templates/erb_test.rb +18 -0
- data/test/test_helper.rb +68 -0
- data/test/transformers/scss_test.rb +49 -0
- metadata +193 -0
@@ -0,0 +1,164 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'tmpdir'
|
3
|
+
require File.expand_path('../node_processor', __FILE__)
|
4
|
+
|
5
|
+
class Condenser
|
6
|
+
class RollupProcessor < NodeProcessor
|
7
|
+
|
8
|
+
ROLLUP_VERSION = '0.56.1'
|
9
|
+
ROLLUP_SOURCE = File.expand_path('../rollup', __FILE__)
|
10
|
+
|
11
|
+
def self.call(environment, input)
|
12
|
+
new.call(environment, input)
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(options = {})
|
16
|
+
@options = options.merge({}).freeze
|
17
|
+
|
18
|
+
# @cache_key = [
|
19
|
+
# self.class.name,
|
20
|
+
# Condenser::VERSION,
|
21
|
+
# SOURCE_VERSION,
|
22
|
+
# @options
|
23
|
+
# ].freeze
|
24
|
+
end
|
25
|
+
|
26
|
+
def call(environment, input)
|
27
|
+
@environment = environment
|
28
|
+
@input = input
|
29
|
+
|
30
|
+
Dir.mktmpdir do |output_dir|
|
31
|
+
@entry = File.join(output_dir, 'entry.js')
|
32
|
+
input_options = {
|
33
|
+
input: @entry,
|
34
|
+
}
|
35
|
+
output_options = {
|
36
|
+
file: File.join(output_dir, 'result.js'),
|
37
|
+
format: 'iife',
|
38
|
+
sourcemap: true
|
39
|
+
}
|
40
|
+
|
41
|
+
exec_runtime(<<-JS)
|
42
|
+
const fs = require('fs');
|
43
|
+
const path = require('path');
|
44
|
+
const stdin = process.stdin;
|
45
|
+
|
46
|
+
|
47
|
+
var buffer = '';
|
48
|
+
stdin.resume();
|
49
|
+
stdin.setEncoding('utf8');
|
50
|
+
stdin.on('data', function (chunk) {
|
51
|
+
buffer += chunk;
|
52
|
+
try {
|
53
|
+
var message = JSON.parse(buffer);
|
54
|
+
stdin.emit('message', message);
|
55
|
+
buffer = '';
|
56
|
+
} catch(e) {
|
57
|
+
if (e.name !== "SyntaxError") {
|
58
|
+
console.log(JSON.stringify({method: 'error', args: [e.name, e.message]}));
|
59
|
+
process.exit(1);
|
60
|
+
}
|
61
|
+
}
|
62
|
+
});
|
63
|
+
|
64
|
+
const rollup = require("#{ROLLUP_SOURCE}");
|
65
|
+
|
66
|
+
function request(method, args) {
|
67
|
+
var promise = new Promise(function(resolve, reject) {
|
68
|
+
stdin.once('message', function(message) {
|
69
|
+
resolve(message['return']);
|
70
|
+
});
|
71
|
+
});
|
72
|
+
|
73
|
+
console.log(JSON.stringify({ method: method, args: args }));
|
74
|
+
|
75
|
+
return promise;
|
76
|
+
}
|
77
|
+
|
78
|
+
const inputOptions = #{JSON.generate(input_options)};
|
79
|
+
inputOptions.plugins = [];
|
80
|
+
inputOptions.plugins.push({
|
81
|
+
name: 'erb',
|
82
|
+
resolveId: function (importee, importer) {
|
83
|
+
return request('resolve', [importee, importer]).then(function(value) {
|
84
|
+
return value;
|
85
|
+
});
|
86
|
+
},
|
87
|
+
load: function(id) {
|
88
|
+
return request('load', [id]).then(function(value) {
|
89
|
+
return value;
|
90
|
+
});
|
91
|
+
}
|
92
|
+
});
|
93
|
+
const outputOptions = #{JSON.generate(output_options)};
|
94
|
+
|
95
|
+
async function build() {
|
96
|
+
try {
|
97
|
+
const bundle = await rollup.rollup(inputOptions);
|
98
|
+
await bundle.write(outputOptions);
|
99
|
+
process.exit(0);
|
100
|
+
} catch(e) {
|
101
|
+
console.log(JSON.stringify({method: 'error', args: [e.name, e.message]}));
|
102
|
+
process.exit(1);
|
103
|
+
}
|
104
|
+
}
|
105
|
+
|
106
|
+
build();
|
107
|
+
JS
|
108
|
+
|
109
|
+
input[:source] = File.read(File.join(output_dir, 'result.js'))
|
110
|
+
input[:source].delete_suffix!("//# sourceMappingURL=result.js.map\n")
|
111
|
+
# asset.map = File.read(File.join(output_dir, 'result.js.map'))
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def exec_runtime(script)
|
116
|
+
io = IO.popen([binary, '-e', script], 'r+')
|
117
|
+
output = ''
|
118
|
+
|
119
|
+
begin
|
120
|
+
while line = io.readline
|
121
|
+
output << line
|
122
|
+
if message = JSON.parse(output)
|
123
|
+
case message['method']
|
124
|
+
when 'resolve'
|
125
|
+
importee, importer = message['args']
|
126
|
+
|
127
|
+
asset = if importer.nil? && importee == @entry
|
128
|
+
@entry
|
129
|
+
else
|
130
|
+
@environment.find!(importee, importer ? File.dirname(@entry == importer ? @input[:source_file] : importer) : nil)&.source_file
|
131
|
+
end
|
132
|
+
|
133
|
+
io.write(JSON.generate({return: asset}))
|
134
|
+
when 'load'
|
135
|
+
if message['args'].first == @entry
|
136
|
+
io.write(JSON.generate({return: {
|
137
|
+
code: @input[:source], map: @input[:map]
|
138
|
+
}}))
|
139
|
+
else
|
140
|
+
asset = @environment.find!(message['args'].first)
|
141
|
+
io.write(JSON.generate({return: {
|
142
|
+
code: asset.source, map: asset.sourcemap
|
143
|
+
}}))
|
144
|
+
end
|
145
|
+
when 'error'
|
146
|
+
raise exec_runtime_error(message['args'][0] + ': ' + message['args'][1])
|
147
|
+
end
|
148
|
+
output = ''
|
149
|
+
end
|
150
|
+
end
|
151
|
+
rescue EOFError
|
152
|
+
end
|
153
|
+
|
154
|
+
io.close
|
155
|
+
|
156
|
+
if $?.success?
|
157
|
+
output
|
158
|
+
else
|
159
|
+
raise exec_runtime_error(output)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'sass/importers'
|
2
|
+
|
3
|
+
class Condenser
|
4
|
+
class SassImporter < Sass::Importers::Base
|
5
|
+
|
6
|
+
GLOB = /(\A|\/)(\*|\*\*\/\*)\z/
|
7
|
+
|
8
|
+
def initialize(env)
|
9
|
+
@environment = env
|
10
|
+
end
|
11
|
+
|
12
|
+
def key(name, options)
|
13
|
+
[self.class.name + ':' + File.dirname(expand_path(name)), File.basename(name)]
|
14
|
+
end
|
15
|
+
|
16
|
+
def public_url(name, sourcemap_directory)
|
17
|
+
Sass::Util.file_uri_from_path(name)
|
18
|
+
end
|
19
|
+
|
20
|
+
def find_relative(name, base, options)
|
21
|
+
name = expand_path(name, base)
|
22
|
+
env = options[:condenser][:environment]
|
23
|
+
accept = extensions.keys.map { |x| options[:condenser][:environment].extensions[x] }
|
24
|
+
|
25
|
+
|
26
|
+
if name.match(GLOB)
|
27
|
+
contents = ""
|
28
|
+
env.resolve(name, accept: accept).sort_by(&:filename).each do |asset|
|
29
|
+
next if asset.filename == options[:filename]
|
30
|
+
contents << "@import \"#{asset.filename}\";\n"
|
31
|
+
end
|
32
|
+
|
33
|
+
return nil if contents == ""
|
34
|
+
Sass::Engine.new(contents, options.merge(
|
35
|
+
filename: name,
|
36
|
+
importer: self,
|
37
|
+
syntax: :scss
|
38
|
+
))
|
39
|
+
else
|
40
|
+
asset = options[:condenser][:environment].find(name, accept: accept)
|
41
|
+
|
42
|
+
if asset
|
43
|
+
asset.process
|
44
|
+
Sass::Engine.new(asset.source, options.merge(
|
45
|
+
filename: asset.filename,
|
46
|
+
importer: self,
|
47
|
+
syntax: extensions[asset.ext]
|
48
|
+
))
|
49
|
+
else
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def find(name, options)
|
56
|
+
if options[:condenser]
|
57
|
+
# globs must be relative
|
58
|
+
return if name =~ GLOB
|
59
|
+
super
|
60
|
+
else
|
61
|
+
super
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Allow .css files to be @import'd
|
66
|
+
def extensions
|
67
|
+
{ '.sass' => :sass, '.scss' => :scss, '.css' => :scss }
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def expand_path(path, base=nil)
|
73
|
+
if path.start_with?('.')
|
74
|
+
File.expand_path(path, File.dirname(base)).delete_prefix(File.expand_path('.') + '/')
|
75
|
+
else
|
76
|
+
File.expand_path(path).delete_prefix(File.expand_path('.') + '/')
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,300 @@
|
|
1
|
+
require 'sass'
|
2
|
+
require 'condenser/processors/sass_importer'
|
3
|
+
|
4
|
+
class Condenser
|
5
|
+
# Processor engine class for the SASS/SCSS compiler. Depends on the `sass` gem.
|
6
|
+
#
|
7
|
+
# For more infomation see:
|
8
|
+
#
|
9
|
+
# https://github.com/sass/sass
|
10
|
+
# https://github.com/rails/sass-rails
|
11
|
+
#
|
12
|
+
class SassProcessor
|
13
|
+
# autoload :CacheStore, 'sprockets/sass_cache_store'
|
14
|
+
|
15
|
+
# Internal: Defines default sass syntax to use. Exposed so the ScssProcessor
|
16
|
+
# may override it.
|
17
|
+
def self.syntax
|
18
|
+
:sass
|
19
|
+
end
|
20
|
+
|
21
|
+
# Public: Return singleton instance with default options.
|
22
|
+
#
|
23
|
+
# Returns SassProcessor object.
|
24
|
+
def self.instance
|
25
|
+
@instance ||= new
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.call(environment, input)
|
29
|
+
instance.call(environment, input)
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.cache_key
|
33
|
+
instance.cache_key
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_reader :cache_key
|
37
|
+
|
38
|
+
# Public: Initialize template with custom options.
|
39
|
+
#
|
40
|
+
# options - Hash
|
41
|
+
# cache_version - String custom cache version. Used to force a cache
|
42
|
+
# change after code changes are made to Sass Functions.
|
43
|
+
#
|
44
|
+
def initialize(options = {}, &block)
|
45
|
+
@cache_version = options[:cache_version]
|
46
|
+
# @cache_key = "#{self.class.name}:#{VERSION}:#{Autoload::Sass::VERSION}:#{@cache_version}".freeze
|
47
|
+
@importer_class = options[:importer] || Condenser::SassImporter || Sass::Importers::Filesystem
|
48
|
+
|
49
|
+
@sass_config = options[:sass_config] || {}
|
50
|
+
@functions = Module.new do
|
51
|
+
include Functions
|
52
|
+
include options[:functions] if options[:functions]
|
53
|
+
class_eval(&block) if block_given?
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def call(environment, input)
|
58
|
+
# context = input[:environment].context_class.new(input)
|
59
|
+
|
60
|
+
engine_options = merge_options({
|
61
|
+
filename: input[:filename],
|
62
|
+
syntax: self.class.syntax,
|
63
|
+
cache_store: nil,#build_cache_store(input, @cache_version),
|
64
|
+
load_paths: environment.path,#.environment.paths.map { |p| @importer_class.new(p.to_s) },
|
65
|
+
importer: @importer_class.new(environment),
|
66
|
+
condenser: {
|
67
|
+
context: environment.new_context_class,
|
68
|
+
environment: environment
|
69
|
+
}
|
70
|
+
})
|
71
|
+
|
72
|
+
engine = Sass::Engine.new(input[:source], engine_options)
|
73
|
+
|
74
|
+
css, map = engine.render_with_sourcemap('')
|
75
|
+
# Utils.module_include(Sass::Script::Functions, @functions) do
|
76
|
+
# engine.render_with_sourcemap('')
|
77
|
+
# end
|
78
|
+
|
79
|
+
css = css.delete_suffix!("\n/*# sourceMappingURL= */\n")
|
80
|
+
|
81
|
+
|
82
|
+
input[:source] = css
|
83
|
+
# input[:map] = map.to_json({})
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
# Public: Build the cache store to be used by the Sass engine.
|
89
|
+
#
|
90
|
+
# input - the input hash.
|
91
|
+
# version - the cache version.
|
92
|
+
#
|
93
|
+
# Override this method if you need to use a different cache than the
|
94
|
+
# Sprockets cache.
|
95
|
+
def build_cache_store(input, version)
|
96
|
+
CacheStore.new(input[:cache], version)
|
97
|
+
end
|
98
|
+
|
99
|
+
def merge_options(options)
|
100
|
+
defaults = @sass_config.dup
|
101
|
+
|
102
|
+
if load_paths = defaults.delete(:load_paths)
|
103
|
+
options[:load_paths] += load_paths
|
104
|
+
end
|
105
|
+
|
106
|
+
options.merge!(defaults)
|
107
|
+
options
|
108
|
+
end
|
109
|
+
|
110
|
+
# Public: Functions injected into Sass context during Sprockets evaluation.
|
111
|
+
#
|
112
|
+
# This module may be extended to add global functionality to all Sprockets
|
113
|
+
# Sass environments. Though, scoping your functions to just your environment
|
114
|
+
# is preferred.
|
115
|
+
#
|
116
|
+
# module Sprockets::SassProcessor::Functions
|
117
|
+
# def asset_path(path, options = {})
|
118
|
+
# end
|
119
|
+
# end
|
120
|
+
#
|
121
|
+
module Functions
|
122
|
+
# Public: Generate a url for asset path.
|
123
|
+
#
|
124
|
+
# Default implementation is deprecated. Currently defaults to
|
125
|
+
# Context#asset_path.
|
126
|
+
#
|
127
|
+
# Will raise NotImplementedError in the future. Users should provide their
|
128
|
+
# own base implementation.
|
129
|
+
#
|
130
|
+
# Returns a Sass::Script::String.
|
131
|
+
def asset_path(path, options = {})
|
132
|
+
path = path.value
|
133
|
+
|
134
|
+
path, _, query, fragment = URI.split(path)[5..8]
|
135
|
+
path = sprockets_context.asset_path(path, options)
|
136
|
+
query = "?#{query}" if query
|
137
|
+
fragment = "##{fragment}" if fragment
|
138
|
+
|
139
|
+
Sass::Script::String.new("#{path}#{query}#{fragment}", :string)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Public: Generate a asset url() link.
|
143
|
+
#
|
144
|
+
# path - Sass::Script::String URL path
|
145
|
+
#
|
146
|
+
# Returns a Sass::Script::String.
|
147
|
+
def asset_url(path, options = {})
|
148
|
+
Sass::Script::String.new("url(#{asset_path(path, options).value})")
|
149
|
+
end
|
150
|
+
|
151
|
+
# Public: Generate url for image path.
|
152
|
+
#
|
153
|
+
# path - Sass::Script::String URL path
|
154
|
+
#
|
155
|
+
# Returns a Sass::Script::String.
|
156
|
+
def image_path(path)
|
157
|
+
asset_path(path, type: :image)
|
158
|
+
end
|
159
|
+
|
160
|
+
# Public: Generate a image url() link.
|
161
|
+
#
|
162
|
+
# path - Sass::Script::String URL path
|
163
|
+
#
|
164
|
+
# Returns a Sass::Script::String.
|
165
|
+
def image_url(path)
|
166
|
+
asset_url(path, type: :image)
|
167
|
+
end
|
168
|
+
|
169
|
+
# Public: Generate url for video path.
|
170
|
+
#
|
171
|
+
# path - Sass::Script::String URL path
|
172
|
+
#
|
173
|
+
# Returns a Sass::Script::String.
|
174
|
+
def video_path(path)
|
175
|
+
asset_path(path, type: :video)
|
176
|
+
end
|
177
|
+
|
178
|
+
# Public: Generate a video url() link.
|
179
|
+
#
|
180
|
+
# path - Sass::Script::String URL path
|
181
|
+
#
|
182
|
+
# Returns a Sass::Script::String.
|
183
|
+
def video_url(path)
|
184
|
+
asset_url(path, type: :video)
|
185
|
+
end
|
186
|
+
|
187
|
+
# Public: Generate url for audio path.
|
188
|
+
#
|
189
|
+
# path - Sass::Script::String URL path
|
190
|
+
#
|
191
|
+
# Returns a Sass::Script::String.
|
192
|
+
def audio_path(path)
|
193
|
+
asset_path(path, type: :audio)
|
194
|
+
end
|
195
|
+
|
196
|
+
# Public: Generate a audio url() link.
|
197
|
+
#
|
198
|
+
# path - Sass::Script::String URL path
|
199
|
+
#
|
200
|
+
# Returns a Sass::Script::String.
|
201
|
+
def audio_url(path)
|
202
|
+
asset_url(path, type: :audio)
|
203
|
+
end
|
204
|
+
|
205
|
+
# Public: Generate url for font path.
|
206
|
+
#
|
207
|
+
# path - Sass::Script::String URL path
|
208
|
+
#
|
209
|
+
# Returns a Sass::Script::String.
|
210
|
+
def font_path(path)
|
211
|
+
asset_path(path, type: :font)
|
212
|
+
end
|
213
|
+
|
214
|
+
# Public: Generate a font url() link.
|
215
|
+
#
|
216
|
+
# path - Sass::Script::String URL path
|
217
|
+
#
|
218
|
+
# Returns a Sass::Script::String.
|
219
|
+
def font_url(path)
|
220
|
+
asset_url(path, type: :font)
|
221
|
+
end
|
222
|
+
|
223
|
+
# Public: Generate url for javascript path.
|
224
|
+
#
|
225
|
+
# path - Sass::Script::String URL path
|
226
|
+
#
|
227
|
+
# Returns a Sass::Script::String.
|
228
|
+
def javascript_path(path)
|
229
|
+
asset_path(path, type: :javascript)
|
230
|
+
end
|
231
|
+
|
232
|
+
# Public: Generate a javascript url() link.
|
233
|
+
#
|
234
|
+
# path - Sass::Script::String URL path
|
235
|
+
#
|
236
|
+
# Returns a Sass::Script::String.
|
237
|
+
def javascript_url(path)
|
238
|
+
asset_url(path, type: :javascript)
|
239
|
+
end
|
240
|
+
|
241
|
+
# Public: Generate url for stylesheet path.
|
242
|
+
#
|
243
|
+
# path - Sass::Script::String URL path
|
244
|
+
#
|
245
|
+
# Returns a Sass::Script::String.
|
246
|
+
def stylesheet_path(path)
|
247
|
+
asset_path(path, type: :stylesheet)
|
248
|
+
end
|
249
|
+
|
250
|
+
# Public: Generate a stylesheet url() link.
|
251
|
+
#
|
252
|
+
# path - Sass::Script::String URL path
|
253
|
+
#
|
254
|
+
# Returns a Sass::Script::String.
|
255
|
+
def stylesheet_url(path)
|
256
|
+
asset_url(path, type: :stylesheet)
|
257
|
+
end
|
258
|
+
|
259
|
+
# Public: Generate a data URI for asset path.
|
260
|
+
#
|
261
|
+
# path - Sass::Script::String logical asset path
|
262
|
+
#
|
263
|
+
# Returns a Sass::Script::String.
|
264
|
+
def asset_data_url(path)
|
265
|
+
url = sprockets_context.asset_data_uri(path.value)
|
266
|
+
Sass::Script::String.new("url(" + url + ")")
|
267
|
+
end
|
268
|
+
|
269
|
+
protected
|
270
|
+
# Public: The Environment.
|
271
|
+
#
|
272
|
+
# Returns Sprockets::Environment.
|
273
|
+
def sprockets_environment
|
274
|
+
options[:sprockets][:environment]
|
275
|
+
end
|
276
|
+
|
277
|
+
# Public: Mutatable set of dependencies.
|
278
|
+
#
|
279
|
+
# Returns a Set.
|
280
|
+
def sprockets_dependencies
|
281
|
+
options[:sprockets][:dependencies]
|
282
|
+
end
|
283
|
+
|
284
|
+
# Deprecated: Get the Context instance. Use APIs on
|
285
|
+
# sprockets_environment or sprockets_dependencies directly.
|
286
|
+
#
|
287
|
+
# Returns a Context instance.
|
288
|
+
def sprockets_context
|
289
|
+
options[:sprockets][:context]
|
290
|
+
end
|
291
|
+
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
class ScssProcessor < SassProcessor
|
296
|
+
def self.syntax
|
297
|
+
:scss
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|