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,37 @@
1
+ # frozen_string_literal: true
2
+ class Condenser::Cache
3
+ class NullStore
4
+
5
+ def get(key)
6
+ nil
7
+ end
8
+
9
+ def set(key, value)
10
+ value
11
+ end
12
+
13
+ def fetch(key)
14
+ value = get(key)
15
+
16
+ if value.nil?
17
+ value = yield
18
+ set(key, value)
19
+ end
20
+ value
21
+ end
22
+
23
+ # Public: Pretty inspect
24
+ #
25
+ # Returns String.
26
+ def inspect
27
+ "#<#{self.class}>"
28
+ end
29
+
30
+ # Public: Simulate clearing the cache
31
+ #
32
+ # Returns true
33
+ def clear(options=nil)
34
+ true
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,272 @@
1
+ # frozen_string_literal: true
2
+ require 'set'
3
+ require 'condenser/errors'
4
+
5
+ class Condenser
6
+ # They are typically accessed by ERB templates. You can mix in custom helpers
7
+ # by injecting them into `Environment#context_class`. Do not mix them into
8
+ # `Context` directly.
9
+ #
10
+ # environment.context_class.class_eval do
11
+ # include MyHelper
12
+ # def asset_url; end
13
+ # end
14
+ #
15
+ # <%= asset_url "foo.png" %>
16
+ #
17
+ # The `Context` also collects dependencies declared by
18
+ # assets. See `DirectiveProcessor` for an example of this.
19
+ class Context
20
+ # Internal: Proxy for ENV that keeps track of the environment variables used
21
+ class ENVProxy < SimpleDelegator
22
+ def initialize(context)
23
+ @context = context
24
+ super(ENV)
25
+ end
26
+
27
+ def [](key)
28
+ @context.depend_on_env(key)
29
+ super
30
+ end
31
+
32
+ def fetch(key, *)
33
+ @context.depend_on_env(key)
34
+ super
35
+ end
36
+ end
37
+
38
+ attr_reader :environment, :filename
39
+
40
+ def initialize(environment)
41
+ # @asset = asset
42
+ @environment = environment
43
+ # puts @environment.inspect, '---'
44
+ end
45
+
46
+ def metadata
47
+ {
48
+ links: @links,
49
+ dependencies: @dependencies
50
+ }
51
+ end
52
+
53
+ def env_proxy
54
+ ENVProxy.new(self)
55
+ end
56
+
57
+ # Returns the environment path that contains the file.
58
+ #
59
+ # If `app/javascripts` and `app/stylesheets` are in your path, and
60
+ # current file is `app/javascripts/foo/bar.js`, `load_path` would
61
+ # return `app/javascripts`.
62
+ attr_reader :load_path
63
+ alias_method :root_path, :load_path
64
+
65
+ # Returns logical path without any file extensions.
66
+ #
67
+ # 'app/javascripts/application.js'
68
+ # # => 'application'
69
+ #
70
+ attr_reader :logical_path
71
+
72
+ # Returns content type of file
73
+ #
74
+ # 'application/javascript'
75
+ # 'text/css'
76
+ #
77
+ attr_reader :content_type
78
+
79
+ # Public: Given a logical path, `resolve` will find and return an Asset URI.
80
+ # Relative paths will also be resolved. An accept type maybe given to
81
+ # restrict the search.
82
+ #
83
+ # resolve("foo.js")
84
+ # # => "file:///path/to/app/javascripts/foo.js?type=application/javascript"
85
+ #
86
+ # resolve("./bar.js")
87
+ # # => "file:///path/to/app/javascripts/bar.js?type=application/javascript"
88
+ #
89
+ # path - String logical or absolute path
90
+ # accept - String content accept type
91
+ #
92
+ # Returns an Asset URI String.
93
+ def resolve(path, **kargs)
94
+ kargs[:base_path] = @dirname
95
+ uri, deps = environment.resolve!(path, **kargs)
96
+ @dependencies.merge(deps)
97
+ uri
98
+ end
99
+
100
+ # Public: Load Asset by AssetURI and track it as a dependency.
101
+ #
102
+ # uri - AssetURI
103
+ #
104
+ # Returns Asset.
105
+ def load(uri)
106
+ asset = environment.load(uri)
107
+ @dependencies.merge(asset.metadata[:dependencies])
108
+ asset
109
+ end
110
+
111
+ # `depend_on` allows you to state a dependency on a file without
112
+ # including it.
113
+ #
114
+ # This is used for caching purposes. Any changes made to
115
+ # the dependency file will invalidate the cache of the
116
+ # source file.
117
+ def depend_on(path)
118
+ if environment.absolute_path?(path) && environment.stat(path)
119
+ @dependencies << environment.build_file_digest_uri(path)
120
+ else
121
+ resolve(path)
122
+ end
123
+ nil
124
+ end
125
+
126
+ # `depend_on_asset` allows you to state an asset dependency
127
+ # without including it.
128
+ #
129
+ # This is used for caching purposes. Any changes that would
130
+ # invalidate the dependency asset will invalidate the source
131
+ # file. Unlike `depend_on`, this will recursively include
132
+ # the target asset's dependencies.
133
+ def depend_on_asset(path)
134
+ load(resolve(path))
135
+ end
136
+
137
+ # `depend_on_env` allows you to state a dependency on an environment
138
+ # variable.
139
+ #
140
+ # This is used for caching purposes. Any changes in the value of the
141
+ # environment variable will invalidate the cache of the source file.
142
+ def depend_on_env(key)
143
+ @dependencies << "env:#{key}"
144
+ end
145
+
146
+ # `link_asset` declares an external dependency on an asset without directly
147
+ # including it. The target asset is returned from this function making it
148
+ # easy to construct a link to it.
149
+ #
150
+ # Returns an Asset or nil.
151
+ def link_asset(path)
152
+ asset = depend_on_asset(path)
153
+ @links << asset.uri
154
+ asset
155
+ end
156
+
157
+ # Returns a `data:` URI with the contents of the asset at the specified
158
+ # path, and marks that path as a dependency of the current file.
159
+ #
160
+ # Uses URI encoding for SVG files, base64 encoding for all the other files.
161
+ #
162
+ # Use `asset_data_uri` from ERB with CSS or JavaScript assets:
163
+ #
164
+ # #logo { background: url(<%= asset_data_uri 'logo.png' %>) }
165
+ #
166
+ # $('<img>').attr('src', '<%= asset_data_uri 'avatar.jpg' %>')
167
+ #
168
+ def asset_data_uri(path)
169
+ asset = depend_on_asset(path)
170
+ if asset.content_type == 'image/svg+xml'
171
+ svg_asset_data_uri(asset)
172
+ else
173
+ base64_asset_data_uri(asset)
174
+ end
175
+ end
176
+
177
+ # Expands logical path to full url to asset.
178
+ #
179
+ # NOTE: This helper is currently not implemented and should be
180
+ # customized by the application. Though, in the future, some
181
+ # basics implemention may be provided with different methods that
182
+ # are required to be overridden.
183
+ def asset_path(path, options = {})
184
+ message = <<-EOS
185
+ Custom asset_path helper is not implemented
186
+
187
+ Extend your environment context with a custom method.
188
+
189
+ environment.context_class.class_eval do
190
+ def asset_path(path, options = {})
191
+ end
192
+ end
193
+ EOS
194
+ raise NotImplementedError, message
195
+ end
196
+
197
+ # Expand logical image asset path.
198
+ def image_path(path)
199
+ asset_path(path, type: :image)
200
+ end
201
+
202
+ # Expand logical video asset path.
203
+ def video_path(path)
204
+ asset_path(path, type: :video)
205
+ end
206
+
207
+ # Expand logical audio asset path.
208
+ def audio_path(path)
209
+ asset_path(path, type: :audio)
210
+ end
211
+
212
+ # Expand logical font asset path.
213
+ def font_path(path)
214
+ asset_path(path, type: :font)
215
+ end
216
+
217
+ # Expand logical javascript asset path.
218
+ def javascript_path(path)
219
+ asset_path(path, type: :javascript)
220
+ end
221
+
222
+ # Expand logical stylesheet asset path.
223
+ def stylesheet_path(path)
224
+ asset_path(path, type: :stylesheet)
225
+ end
226
+
227
+ protected
228
+
229
+ # Returns a URI-encoded data URI (always "-quoted).
230
+ def svg_asset_data_uri(asset)
231
+ svg = asset.source.dup
232
+ optimize_svg_for_uri_escaping!(svg)
233
+ data = URI.encode_www_form_component(svg)
234
+ optimize_quoted_uri_escapes!(data)
235
+ "\"data:#{asset.content_type};charset=utf-8,#{data}\""
236
+ end
237
+
238
+ # Returns a Base64-encoded data URI.
239
+ def base64_asset_data_uri(asset)
240
+ data = URI.encode_www_form_component(EncodingUtils.base64(asset.source))
241
+ "data:#{asset.content_type};base64,#{data}"
242
+ end
243
+
244
+ # Optimizes an SVG for being URI-escaped.
245
+ #
246
+ # This method only performs these basic but crucial optimizations:
247
+ # * Replaces " with ', because ' does not need escaping.
248
+ # * Removes comments, meta, doctype, and newlines.
249
+ # * Collapses whitespace.
250
+ def optimize_svg_for_uri_escaping!(svg)
251
+ # Remove comments, xml meta, and doctype
252
+ svg.gsub!(/<!--.*?-->|<\?.*?\?>|<!.*?>/m, '')
253
+ # Replace consecutive whitespace and newlines with a space
254
+ svg.gsub!(/\s+/, ' ')
255
+ # Collapse inter-tag whitespace
256
+ svg.gsub!('> <', '><')
257
+ # Replace " with '
258
+ svg.gsub!(/([\w:])="(.*?)"/, "\\1='\\2'")
259
+ svg.strip!
260
+ end
261
+
262
+ # Un-escapes characters in the given URI-escaped string that do not need
263
+ # escaping in "-quoted data URIs.
264
+ def optimize_quoted_uri_escapes!(escaped)
265
+ escaped.gsub!('%3D', '=')
266
+ escaped.gsub!('%3A', ':')
267
+ escaped.gsub!('%2F', '/')
268
+ escaped.gsub!('%27', "'")
269
+ escaped.tr!('+', ' ')
270
+ end
271
+ end
272
+ end
@@ -0,0 +1,155 @@
1
+ class Condenser
2
+ module EncodingUtils
3
+
4
+ # Internal: Mapping unicode encodings to byte order markers.
5
+ BOM = {
6
+ ::Encoding::UTF_32LE => [0xFF, 0xFE, 0x00, 0x00],
7
+ ::Encoding::UTF_32BE => [0x00, 0x00, 0xFE, 0xFF],
8
+ ::Encoding::UTF_8 => [0xEF, 0xBB, 0xBF],
9
+ ::Encoding::UTF_16LE => [0xFF, 0xFE],
10
+ ::Encoding::UTF_16BE => [0xFE, 0xFF]
11
+ }
12
+
13
+ # Public: Basic string detecter.
14
+ #
15
+ # Attempts to parse any Unicode BOM otherwise falls back to the
16
+ # environment's external encoding.
17
+ #
18
+ # str - ASCII-8BIT encoded String
19
+ #
20
+ # Returns encoded String.
21
+ def detect(str)
22
+ str = detect_unicode_bom(str)
23
+
24
+ # Fallback to environment's external encoding
25
+ if str.encoding == Encoding::BINARY
26
+ str.force_encoding(Encoding.default_external)
27
+ end
28
+
29
+ str
30
+ end
31
+
32
+ # Public: Detect Unicode string.
33
+ #
34
+ # Attempts to parse Unicode BOM and falls back to UTF-8.
35
+ #
36
+ # str - ASCII-8BIT encoded String
37
+ #
38
+ # Returns encoded String.
39
+ def detect_unicode(str)
40
+ str = detect_unicode_bom(str)
41
+
42
+ # Fallback to UTF-8
43
+ if str.encoding == Encoding::BINARY
44
+ str.force_encoding(Encoding::UTF_8)
45
+ end
46
+
47
+ str
48
+ end
49
+
50
+ # Public: Detect and strip BOM from possible unicode string.
51
+ #
52
+ # str - ASCII-8BIT encoded String
53
+ #
54
+ # Returns UTF 8/16/32 encoded String without BOM or the original String if
55
+ # no BOM was present.
56
+ def detect_unicode_bom(str)
57
+ bom_bytes = str.byteslice(0, 4).bytes.to_a
58
+
59
+ BOM.each do |encoding, bytes|
60
+ if bom_bytes[0, bytes.size] == bytes
61
+ str = str.dup
62
+ str.force_encoding(Encoding::BINARY)
63
+ str.slice!(0, bytes.size)
64
+ str.force_encoding(encoding)
65
+ return str
66
+ end
67
+ end
68
+
69
+ return str
70
+ end
71
+
72
+ # Public: Detect and strip @charset from CSS style sheet.
73
+ #
74
+ # str - String.
75
+ #
76
+ # Returns a encoded String.
77
+ def detect_css(str)
78
+ str = detect_unicode_bom(str)
79
+
80
+ if name = scan_css_charset(str)
81
+ encoding = Encoding.find(name)
82
+ str = str.dup
83
+ str.force_encoding(encoding)
84
+ len = "@charset \"#{name}\";".encode(encoding).size
85
+ str.slice!(0, len)
86
+ str
87
+ end
88
+
89
+ # Fallback to UTF-8
90
+ if str.encoding == Encoding::BINARY
91
+ str.force_encoding(Encoding::UTF_8)
92
+ end
93
+
94
+ str
95
+ end
96
+
97
+ # Internal: @charset bytes
98
+ CHARSET_START = [0x40, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x20, 0x22]
99
+ CHARSET_SIZE = CHARSET_START.size
100
+
101
+ # Internal: Scan binary CSS string for @charset encoding name.
102
+ #
103
+ # str - ASCII-8BIT encoded String
104
+ #
105
+ # Returns encoding String name or nil.
106
+ def scan_css_charset(str)
107
+ buf = []
108
+ i = 0
109
+
110
+ str.each_byte.each do |byte|
111
+ # Halt on line breaks
112
+ break if byte == 0x0A || byte == 0x0D
113
+
114
+ # Only ascii bytes
115
+ next unless 0x0 < byte && byte <= 0xFF
116
+
117
+ if i < CHARSET_SIZE
118
+ elsif i == CHARSET_SIZE
119
+ if buf == CHARSET_START
120
+ buf = []
121
+ else
122
+ break
123
+ end
124
+ elsif byte == 0x22
125
+ return buf.pack('C*')
126
+ end
127
+
128
+ buf << byte
129
+ i += 1
130
+ end
131
+
132
+ nil
133
+ end
134
+
135
+ # Public: Detect charset from HTML document.
136
+ #
137
+ # Attempts to parse any Unicode BOM otherwise attempt Charlock detection
138
+ # and finally falls back to the environment's external encoding.
139
+ #
140
+ # str - String.
141
+ #
142
+ # Returns a encoded String.
143
+ def detect_html(str)
144
+ str = detect_unicode_bom(str)
145
+
146
+ # Fallback to environment's external encoding
147
+ if str.encoding == Encoding::BINARY
148
+ str.force_encoding(Encoding.default_external)
149
+ end
150
+
151
+ str
152
+ end
153
+
154
+ end
155
+ end