condenser 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +1 -0
  4. data/lib/condenser.rb +108 -0
  5. data/lib/condenser/asset.rb +221 -0
  6. data/lib/condenser/cache/memory_store.rb +92 -0
  7. data/lib/condenser/cache/null_store.rb +37 -0
  8. data/lib/condenser/context.rb +272 -0
  9. data/lib/condenser/encoding_utils.rb +155 -0
  10. data/lib/condenser/environment.rb +50 -0
  11. data/lib/condenser/errors.rb +11 -0
  12. data/lib/condenser/export.rb +68 -0
  13. data/lib/condenser/manifest.rb +89 -0
  14. data/lib/condenser/pipeline.rb +82 -0
  15. data/lib/condenser/processors/babel.min.js +25 -0
  16. data/lib/condenser/processors/babel_processor.rb +87 -0
  17. data/lib/condenser/processors/node_processor.rb +38 -0
  18. data/lib/condenser/processors/rollup.js +24083 -0
  19. data/lib/condenser/processors/rollup_processor.rb +164 -0
  20. data/lib/condenser/processors/sass_importer.rb +81 -0
  21. data/lib/condenser/processors/sass_processor.rb +300 -0
  22. data/lib/condenser/resolve.rb +202 -0
  23. data/lib/condenser/server.rb +307 -0
  24. data/lib/condenser/templating_engine/erb.rb +21 -0
  25. data/lib/condenser/utils.rb +32 -0
  26. data/lib/condenser/version.rb +3 -0
  27. data/lib/condenser/writers/file_writer.rb +28 -0
  28. data/lib/condenser/writers/zlib_writer.rb +42 -0
  29. data/test/cache_test.rb +24 -0
  30. data/test/environment_test.rb +49 -0
  31. data/test/manifest_test.rb +513 -0
  32. data/test/pipeline_test.rb +31 -0
  33. data/test/preprocessor/babel_test.rb +21 -0
  34. data/test/processors/rollup_test.rb +71 -0
  35. data/test/resolve_test.rb +105 -0
  36. data/test/server_test.rb +361 -0
  37. data/test/templates/erb_test.rb +18 -0
  38. data/test/test_helper.rb +68 -0
  39. data/test/transformers/scss_test.rb +49 -0
  40. 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