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