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.
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