sprockets 4.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/CHANGELOG.md +72 -0
- data/README.md +665 -0
- data/bin/sprockets +93 -0
- data/lib/rake/sprocketstask.rb +153 -0
- data/lib/sprockets.rb +229 -0
- data/lib/sprockets/add_source_map_comment_to_asset_processor.rb +60 -0
- data/lib/sprockets/asset.rb +202 -0
- data/lib/sprockets/autoload.rb +16 -0
- data/lib/sprockets/autoload/babel.rb +8 -0
- data/lib/sprockets/autoload/closure.rb +8 -0
- data/lib/sprockets/autoload/coffee_script.rb +8 -0
- data/lib/sprockets/autoload/eco.rb +8 -0
- data/lib/sprockets/autoload/ejs.rb +8 -0
- data/lib/sprockets/autoload/jsminc.rb +8 -0
- data/lib/sprockets/autoload/sass.rb +8 -0
- data/lib/sprockets/autoload/sassc.rb +8 -0
- data/lib/sprockets/autoload/uglifier.rb +8 -0
- data/lib/sprockets/autoload/yui.rb +8 -0
- data/lib/sprockets/autoload/zopfli.rb +7 -0
- data/lib/sprockets/babel_processor.rb +66 -0
- data/lib/sprockets/base.rb +147 -0
- data/lib/sprockets/bower.rb +61 -0
- data/lib/sprockets/bundle.rb +105 -0
- data/lib/sprockets/cache.rb +271 -0
- data/lib/sprockets/cache/file_store.rb +208 -0
- data/lib/sprockets/cache/memory_store.rb +75 -0
- data/lib/sprockets/cache/null_store.rb +54 -0
- data/lib/sprockets/cached_environment.rb +64 -0
- data/lib/sprockets/closure_compressor.rb +48 -0
- data/lib/sprockets/coffee_script_processor.rb +39 -0
- data/lib/sprockets/compressing.rb +134 -0
- data/lib/sprockets/configuration.rb +79 -0
- data/lib/sprockets/context.rb +304 -0
- data/lib/sprockets/dependencies.rb +74 -0
- data/lib/sprockets/digest_utils.rb +200 -0
- data/lib/sprockets/directive_processor.rb +414 -0
- data/lib/sprockets/eco_processor.rb +33 -0
- data/lib/sprockets/ejs_processor.rb +32 -0
- data/lib/sprockets/encoding_utils.rb +262 -0
- data/lib/sprockets/environment.rb +46 -0
- data/lib/sprockets/erb_processor.rb +37 -0
- data/lib/sprockets/errors.rb +12 -0
- data/lib/sprockets/exporters/base.rb +71 -0
- data/lib/sprockets/exporters/file_exporter.rb +24 -0
- data/lib/sprockets/exporters/zlib_exporter.rb +33 -0
- data/lib/sprockets/exporters/zopfli_exporter.rb +14 -0
- data/lib/sprockets/exporting.rb +73 -0
- data/lib/sprockets/file_reader.rb +16 -0
- data/lib/sprockets/http_utils.rb +135 -0
- data/lib/sprockets/jsminc_compressor.rb +32 -0
- data/lib/sprockets/jst_processor.rb +50 -0
- data/lib/sprockets/loader.rb +345 -0
- data/lib/sprockets/manifest.rb +338 -0
- data/lib/sprockets/manifest_utils.rb +48 -0
- data/lib/sprockets/mime.rb +96 -0
- data/lib/sprockets/npm.rb +52 -0
- data/lib/sprockets/path_dependency_utils.rb +77 -0
- data/lib/sprockets/path_digest_utils.rb +48 -0
- data/lib/sprockets/path_utils.rb +367 -0
- data/lib/sprockets/paths.rb +82 -0
- data/lib/sprockets/preprocessors/default_source_map.rb +49 -0
- data/lib/sprockets/processing.rb +228 -0
- data/lib/sprockets/processor_utils.rb +169 -0
- data/lib/sprockets/resolve.rb +295 -0
- data/lib/sprockets/sass_cache_store.rb +30 -0
- data/lib/sprockets/sass_compressor.rb +63 -0
- data/lib/sprockets/sass_functions.rb +3 -0
- data/lib/sprockets/sass_importer.rb +3 -0
- data/lib/sprockets/sass_processor.rb +313 -0
- data/lib/sprockets/sassc_compressor.rb +56 -0
- data/lib/sprockets/sassc_processor.rb +297 -0
- data/lib/sprockets/server.rb +295 -0
- data/lib/sprockets/source_map_processor.rb +66 -0
- data/lib/sprockets/source_map_utils.rb +483 -0
- data/lib/sprockets/transformers.rb +173 -0
- data/lib/sprockets/uglifier_compressor.rb +66 -0
- data/lib/sprockets/unloaded_asset.rb +139 -0
- data/lib/sprockets/uri_tar.rb +99 -0
- data/lib/sprockets/uri_utils.rb +191 -0
- data/lib/sprockets/utils.rb +202 -0
- data/lib/sprockets/utils/gzip.rb +99 -0
- data/lib/sprockets/version.rb +4 -0
- data/lib/sprockets/yui_compressor.rb +56 -0
- metadata +444 -0
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'sprockets/autoload'
|
3
|
+
require 'sprockets/source_map_utils'
|
4
|
+
|
5
|
+
module Sprockets
|
6
|
+
# Processor engine class for the CoffeeScript compiler.
|
7
|
+
# Depends on the `coffee-script` and `coffee-script-source` gems.
|
8
|
+
#
|
9
|
+
# For more infomation see:
|
10
|
+
#
|
11
|
+
# https://github.com/rails/ruby-coffee-script
|
12
|
+
#
|
13
|
+
module CoffeeScriptProcessor
|
14
|
+
VERSION = '2'
|
15
|
+
|
16
|
+
def self.cache_key
|
17
|
+
@cache_key ||= "#{name}:#{Autoload::CoffeeScript::Source.version}:#{VERSION}".freeze
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.call(input)
|
21
|
+
data = input[:data]
|
22
|
+
|
23
|
+
js, map = input[:cache].fetch([self.cache_key, data]) do
|
24
|
+
result = Autoload::CoffeeScript.compile(
|
25
|
+
data,
|
26
|
+
sourceMap: "v3",
|
27
|
+
sourceFiles: [File.basename(input[:filename])],
|
28
|
+
generatedFile: input[:filename]
|
29
|
+
)
|
30
|
+
[result['js'], JSON.parse(result['v3SourceMap'])]
|
31
|
+
end
|
32
|
+
|
33
|
+
map = SourceMapUtils.format_source_map(map, input)
|
34
|
+
map = SourceMapUtils.combine_source_maps(input[:metadata][:map], map)
|
35
|
+
|
36
|
+
{ data: js, map: map }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'sprockets/utils'
|
3
|
+
|
4
|
+
module Sprockets
|
5
|
+
# `Compressing` is an internal mixin whose public methods are exposed on
|
6
|
+
# the `Environment` and `CachedEnvironment` classes.
|
7
|
+
module Compressing
|
8
|
+
include Utils
|
9
|
+
|
10
|
+
def compressors
|
11
|
+
config[:compressors]
|
12
|
+
end
|
13
|
+
|
14
|
+
# Public: Register a new compressor `klass` at `sym` for `mime_type`.
|
15
|
+
#
|
16
|
+
# Registering a processor allows it to be looked up by `sym` later when
|
17
|
+
# assigning a JavaScript or CSS compressor.
|
18
|
+
#
|
19
|
+
# Compressors only operate on JavaScript and CSS. If you want to compress a
|
20
|
+
# different type of asset, use a processor instead.
|
21
|
+
#
|
22
|
+
# Examples
|
23
|
+
#
|
24
|
+
# register_compressor 'text/css', :my_sass, MySassCompressor
|
25
|
+
# css_compressor = :my_sass
|
26
|
+
#
|
27
|
+
# mime_type - String MIME Type (one of: 'test/css' or 'application/javascript').
|
28
|
+
# sym - Symbol registration address.
|
29
|
+
# klass - The compressor class.
|
30
|
+
#
|
31
|
+
# Returns nothing.
|
32
|
+
def register_compressor(mime_type, sym, klass)
|
33
|
+
self.config = hash_reassoc(config, :compressors, mime_type) do |compressors|
|
34
|
+
compressors[sym] = klass
|
35
|
+
compressors
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Return CSS compressor or nil if none is set
|
40
|
+
def css_compressor
|
41
|
+
if defined? @css_compressor
|
42
|
+
@css_compressor
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Assign a compressor to run on `text/css` assets.
|
47
|
+
#
|
48
|
+
# The compressor object must respond to `compress`.
|
49
|
+
def css_compressor=(compressor)
|
50
|
+
unregister_bundle_processor 'text/css', @css_compressor if defined? @css_compressor
|
51
|
+
@css_compressor = nil
|
52
|
+
return unless compressor
|
53
|
+
|
54
|
+
if compressor.is_a?(Symbol)
|
55
|
+
@css_compressor = klass = config[:compressors]['text/css'][compressor] || raise(Error, "unknown compressor: #{compressor}")
|
56
|
+
elsif compressor.respond_to?(:compress)
|
57
|
+
klass = proc { |input| compressor.compress(input[:data]) }
|
58
|
+
@css_compressor = :css_compressor
|
59
|
+
else
|
60
|
+
@css_compressor = klass = compressor
|
61
|
+
end
|
62
|
+
|
63
|
+
register_bundle_processor 'text/css', klass
|
64
|
+
end
|
65
|
+
|
66
|
+
# Return JS compressor or nil if none is set
|
67
|
+
def js_compressor
|
68
|
+
if defined? @js_compressor
|
69
|
+
@js_compressor
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Assign a compressor to run on `application/javascript` assets.
|
74
|
+
#
|
75
|
+
# The compressor object must respond to `compress`.
|
76
|
+
def js_compressor=(compressor)
|
77
|
+
unregister_bundle_processor 'application/javascript', @js_compressor if defined? @js_compressor
|
78
|
+
@js_compressor = nil
|
79
|
+
return unless compressor
|
80
|
+
|
81
|
+
if compressor.is_a?(Symbol)
|
82
|
+
@js_compressor = klass = config[:compressors]['application/javascript'][compressor] || raise(Error, "unknown compressor: #{compressor}")
|
83
|
+
elsif compressor.respond_to?(:compress)
|
84
|
+
klass = proc { |input| compressor.compress(input[:data]) }
|
85
|
+
@js_compressor = :js_compressor
|
86
|
+
else
|
87
|
+
@js_compressor = klass = compressor
|
88
|
+
end
|
89
|
+
|
90
|
+
register_bundle_processor 'application/javascript', klass
|
91
|
+
end
|
92
|
+
|
93
|
+
# Public: Checks if Gzip is enabled.
|
94
|
+
def gzip?
|
95
|
+
config[:gzip_enabled]
|
96
|
+
end
|
97
|
+
|
98
|
+
# Public: Checks if Gzip is disabled.
|
99
|
+
def skip_gzip?
|
100
|
+
!gzip?
|
101
|
+
end
|
102
|
+
|
103
|
+
# Public: Enable or disable the creation of Gzip files.
|
104
|
+
#
|
105
|
+
# To disable gzip generation set to a falsey value:
|
106
|
+
#
|
107
|
+
# environment.gzip = false
|
108
|
+
#
|
109
|
+
# To enable set to a truthy value. By default zlib wil
|
110
|
+
# be used to gzip assets. If you have the Zopfli gem
|
111
|
+
# installed you can specify the zopfli algorithm to be used
|
112
|
+
# instead:
|
113
|
+
#
|
114
|
+
# environment.gzip = :zopfli
|
115
|
+
#
|
116
|
+
def gzip=(gzip)
|
117
|
+
self.config = config.merge(gzip_enabled: gzip).freeze
|
118
|
+
|
119
|
+
case gzip
|
120
|
+
when false, nil
|
121
|
+
self.unregister_exporter Exporters::ZlibExporter
|
122
|
+
self.unregister_exporter Exporters::ZopfliExporter
|
123
|
+
when :zopfli
|
124
|
+
self.unregister_exporter Exporters::ZlibExporter
|
125
|
+
self.register_exporter '*/*', Exporters::ZopfliExporter
|
126
|
+
else
|
127
|
+
self.unregister_exporter Exporters::ZopfliExporter
|
128
|
+
self.register_exporter '*/*', Exporters::ZlibExporter
|
129
|
+
end
|
130
|
+
|
131
|
+
gzip
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'sprockets/compressing'
|
3
|
+
require 'sprockets/dependencies'
|
4
|
+
require 'sprockets/mime'
|
5
|
+
require 'sprockets/paths'
|
6
|
+
require 'sprockets/processing'
|
7
|
+
require 'sprockets/exporting'
|
8
|
+
require 'sprockets/transformers'
|
9
|
+
require 'sprockets/utils'
|
10
|
+
|
11
|
+
module Sprockets
|
12
|
+
module Configuration
|
13
|
+
include Paths, Mime, Transformers, Processing, Exporting, Compressing, Dependencies, Utils
|
14
|
+
|
15
|
+
def initialize_configuration(parent)
|
16
|
+
@config = parent.config
|
17
|
+
@logger = parent.logger
|
18
|
+
@context_class = Class.new(parent.context_class)
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :config
|
22
|
+
|
23
|
+
def config=(config)
|
24
|
+
raise TypeError, "can't assign mutable config" unless config.frozen?
|
25
|
+
@config = config
|
26
|
+
end
|
27
|
+
|
28
|
+
# Get and set `Logger` instance.
|
29
|
+
attr_accessor :logger
|
30
|
+
|
31
|
+
# The `Environment#version` is a custom value used for manually
|
32
|
+
# expiring all asset caches.
|
33
|
+
#
|
34
|
+
# Sprockets is able to track most file and directory changes and
|
35
|
+
# will take care of expiring the cache for you. However, its
|
36
|
+
# impossible to know when any custom helpers change that you mix
|
37
|
+
# into the `Context`.
|
38
|
+
#
|
39
|
+
# It would be wise to increment this value anytime you make a
|
40
|
+
# configuration change to the `Environment` object.
|
41
|
+
def version
|
42
|
+
config[:version]
|
43
|
+
end
|
44
|
+
|
45
|
+
# Assign an environment version.
|
46
|
+
#
|
47
|
+
# environment.version = '2.0'
|
48
|
+
#
|
49
|
+
def version=(version)
|
50
|
+
self.config = hash_reassoc(config, :version) { version.dup }
|
51
|
+
end
|
52
|
+
|
53
|
+
# Public: Returns a `Digest` implementation class.
|
54
|
+
#
|
55
|
+
# Defaults to `Digest::SHA256`.
|
56
|
+
def digest_class
|
57
|
+
config[:digest_class]
|
58
|
+
end
|
59
|
+
|
60
|
+
# Deprecated: Assign a `Digest` implementation class. This maybe any Ruby
|
61
|
+
# `Digest::` implementation such as `Digest::SHA256` or
|
62
|
+
# `Digest::MD5`.
|
63
|
+
#
|
64
|
+
# environment.digest_class = Digest::MD5
|
65
|
+
#
|
66
|
+
def digest_class=(klass)
|
67
|
+
self.config = config.merge(digest_class: klass).freeze
|
68
|
+
end
|
69
|
+
|
70
|
+
# This class maybe mutated and mixed in with custom helpers.
|
71
|
+
#
|
72
|
+
# environment.context_class.instance_eval do
|
73
|
+
# include MyHelpers
|
74
|
+
# def asset_url; end
|
75
|
+
# end
|
76
|
+
#
|
77
|
+
attr_reader :context_class
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,304 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'rack/utils'
|
3
|
+
require 'set'
|
4
|
+
require 'sprockets/errors'
|
5
|
+
|
6
|
+
module Sprockets
|
7
|
+
# They are typically accessed by ERB templates. You can mix in custom helpers
|
8
|
+
# by injecting them into `Environment#context_class`. Do not mix them into
|
9
|
+
# `Context` directly.
|
10
|
+
#
|
11
|
+
# environment.context_class.class_eval do
|
12
|
+
# include MyHelper
|
13
|
+
# def asset_url; end
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# <%= asset_url "foo.png" %>
|
17
|
+
#
|
18
|
+
# The `Context` also collects dependencies declared by
|
19
|
+
# assets. See `DirectiveProcessor` for an example of this.
|
20
|
+
class Context
|
21
|
+
# Internal: Proxy for ENV that keeps track of the environment variables used
|
22
|
+
class ENVProxy < SimpleDelegator
|
23
|
+
def initialize(context)
|
24
|
+
@context = context
|
25
|
+
super(ENV)
|
26
|
+
end
|
27
|
+
|
28
|
+
def [](key)
|
29
|
+
@context.depend_on_env(key)
|
30
|
+
super
|
31
|
+
end
|
32
|
+
|
33
|
+
def fetch(key, *)
|
34
|
+
@context.depend_on_env(key)
|
35
|
+
super
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
attr_reader :environment, :filename
|
40
|
+
|
41
|
+
def initialize(input)
|
42
|
+
@environment = input[:environment]
|
43
|
+
@metadata = input[:metadata]
|
44
|
+
@load_path = input[:load_path]
|
45
|
+
@logical_path = input[:name]
|
46
|
+
@filename = input[:filename]
|
47
|
+
@dirname = File.dirname(@filename)
|
48
|
+
@content_type = input[:content_type]
|
49
|
+
|
50
|
+
@required = Set.new(@metadata[:required])
|
51
|
+
@stubbed = Set.new(@metadata[:stubbed])
|
52
|
+
@links = Set.new(@metadata[:links])
|
53
|
+
@dependencies = Set.new(input[:metadata][:dependencies])
|
54
|
+
end
|
55
|
+
|
56
|
+
def metadata
|
57
|
+
{ required: @required,
|
58
|
+
stubbed: @stubbed,
|
59
|
+
links: @links,
|
60
|
+
dependencies: @dependencies }
|
61
|
+
end
|
62
|
+
|
63
|
+
def env_proxy
|
64
|
+
ENVProxy.new(self)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns the environment path that contains the file.
|
68
|
+
#
|
69
|
+
# If `app/javascripts` and `app/stylesheets` are in your path, and
|
70
|
+
# current file is `app/javascripts/foo/bar.js`, `load_path` would
|
71
|
+
# return `app/javascripts`.
|
72
|
+
attr_reader :load_path
|
73
|
+
alias_method :root_path, :load_path
|
74
|
+
|
75
|
+
# Returns logical path without any file extensions.
|
76
|
+
#
|
77
|
+
# 'app/javascripts/application.js'
|
78
|
+
# # => 'application'
|
79
|
+
#
|
80
|
+
attr_reader :logical_path
|
81
|
+
|
82
|
+
# Returns content type of file
|
83
|
+
#
|
84
|
+
# 'application/javascript'
|
85
|
+
# 'text/css'
|
86
|
+
#
|
87
|
+
attr_reader :content_type
|
88
|
+
|
89
|
+
# Public: Given a logical path, `resolve` will find and return an Asset URI.
|
90
|
+
# Relative paths will also be resolved. An accept type maybe given to
|
91
|
+
# restrict the search.
|
92
|
+
#
|
93
|
+
# resolve("foo.js")
|
94
|
+
# # => "file:///path/to/app/javascripts/foo.js?type=application/javascript"
|
95
|
+
#
|
96
|
+
# resolve("./bar.js")
|
97
|
+
# # => "file:///path/to/app/javascripts/bar.js?type=application/javascript"
|
98
|
+
#
|
99
|
+
# path - String logical or absolute path
|
100
|
+
# accept - String content accept type
|
101
|
+
#
|
102
|
+
# Returns an Asset URI String.
|
103
|
+
def resolve(path, **kargs)
|
104
|
+
kargs[:base_path] = @dirname
|
105
|
+
uri, deps = environment.resolve!(path, **kargs)
|
106
|
+
@dependencies.merge(deps)
|
107
|
+
uri
|
108
|
+
end
|
109
|
+
|
110
|
+
# Public: Load Asset by AssetURI and track it as a dependency.
|
111
|
+
#
|
112
|
+
# uri - AssetURI
|
113
|
+
#
|
114
|
+
# Returns Asset.
|
115
|
+
def load(uri)
|
116
|
+
asset = environment.load(uri)
|
117
|
+
@dependencies.merge(asset.metadata[:dependencies])
|
118
|
+
asset
|
119
|
+
end
|
120
|
+
|
121
|
+
# `depend_on` allows you to state a dependency on a file without
|
122
|
+
# including it.
|
123
|
+
#
|
124
|
+
# This is used for caching purposes. Any changes made to
|
125
|
+
# the dependency file will invalidate the cache of the
|
126
|
+
# source file.
|
127
|
+
def depend_on(path)
|
128
|
+
if environment.absolute_path?(path) && environment.stat(path)
|
129
|
+
@dependencies << environment.build_file_digest_uri(path)
|
130
|
+
else
|
131
|
+
resolve(path)
|
132
|
+
end
|
133
|
+
nil
|
134
|
+
end
|
135
|
+
|
136
|
+
# `depend_on_asset` allows you to state an asset dependency
|
137
|
+
# without including it.
|
138
|
+
#
|
139
|
+
# This is used for caching purposes. Any changes that would
|
140
|
+
# invalidate the dependency asset will invalidate the source
|
141
|
+
# file. Unlike `depend_on`, this will recursively include
|
142
|
+
# the target asset's dependencies.
|
143
|
+
def depend_on_asset(path)
|
144
|
+
load(resolve(path))
|
145
|
+
end
|
146
|
+
|
147
|
+
# `depend_on_env` allows you to state a dependency on an environment
|
148
|
+
# variable.
|
149
|
+
#
|
150
|
+
# This is used for caching purposes. Any changes in the value of the
|
151
|
+
# environment variable will invalidate the cache of the source file.
|
152
|
+
def depend_on_env(key)
|
153
|
+
@dependencies << "env:#{key}"
|
154
|
+
end
|
155
|
+
|
156
|
+
# `require_asset` declares `path` as a dependency of the file. The
|
157
|
+
# dependency will be inserted before the file and will only be
|
158
|
+
# included once.
|
159
|
+
#
|
160
|
+
# If ERB processing is enabled, you can use it to dynamically
|
161
|
+
# require assets.
|
162
|
+
#
|
163
|
+
# <%= require_asset "#{framework}.js" %>
|
164
|
+
#
|
165
|
+
def require_asset(path)
|
166
|
+
@required << resolve(path, accept: @content_type, pipeline: :self)
|
167
|
+
nil
|
168
|
+
end
|
169
|
+
|
170
|
+
# `stub_asset` blacklists `path` from being included in the bundle.
|
171
|
+
# `path` must be an asset which may or may not already be included
|
172
|
+
# in the bundle.
|
173
|
+
def stub_asset(path)
|
174
|
+
@stubbed << resolve(path, accept: @content_type, pipeline: :self)
|
175
|
+
nil
|
176
|
+
end
|
177
|
+
|
178
|
+
# `link_asset` declares an external dependency on an asset without directly
|
179
|
+
# including it. The target asset is returned from this function making it
|
180
|
+
# easy to construct a link to it.
|
181
|
+
#
|
182
|
+
# Returns an Asset or nil.
|
183
|
+
def link_asset(path)
|
184
|
+
asset = depend_on_asset(path)
|
185
|
+
@links << asset.uri
|
186
|
+
asset
|
187
|
+
end
|
188
|
+
|
189
|
+
# Returns a `data:` URI with the contents of the asset at the specified
|
190
|
+
# path, and marks that path as a dependency of the current file.
|
191
|
+
#
|
192
|
+
# Uses URI encoding for SVG files, base64 encoding for all the other files.
|
193
|
+
#
|
194
|
+
# Use `asset_data_uri` from ERB with CSS or JavaScript assets:
|
195
|
+
#
|
196
|
+
# #logo { background: url(<%= asset_data_uri 'logo.png' %>) }
|
197
|
+
#
|
198
|
+
# $('<img>').attr('src', '<%= asset_data_uri 'avatar.jpg' %>')
|
199
|
+
#
|
200
|
+
def asset_data_uri(path)
|
201
|
+
asset = depend_on_asset(path)
|
202
|
+
if asset.content_type == 'image/svg+xml'
|
203
|
+
svg_asset_data_uri(asset)
|
204
|
+
else
|
205
|
+
base64_asset_data_uri(asset)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# Expands logical path to full url to asset.
|
210
|
+
#
|
211
|
+
# NOTE: This helper is currently not implemented and should be
|
212
|
+
# customized by the application. Though, in the future, some
|
213
|
+
# basics implemention may be provided with different methods that
|
214
|
+
# are required to be overridden.
|
215
|
+
def asset_path(path, options = {})
|
216
|
+
message = <<-EOS
|
217
|
+
Custom asset_path helper is not implemented
|
218
|
+
|
219
|
+
Extend your environment context with a custom method.
|
220
|
+
|
221
|
+
environment.context_class.class_eval do
|
222
|
+
def asset_path(path, options = {})
|
223
|
+
end
|
224
|
+
end
|
225
|
+
EOS
|
226
|
+
raise NotImplementedError, message
|
227
|
+
end
|
228
|
+
|
229
|
+
# Expand logical image asset path.
|
230
|
+
def image_path(path)
|
231
|
+
asset_path(path, type: :image)
|
232
|
+
end
|
233
|
+
|
234
|
+
# Expand logical video asset path.
|
235
|
+
def video_path(path)
|
236
|
+
asset_path(path, type: :video)
|
237
|
+
end
|
238
|
+
|
239
|
+
# Expand logical audio asset path.
|
240
|
+
def audio_path(path)
|
241
|
+
asset_path(path, type: :audio)
|
242
|
+
end
|
243
|
+
|
244
|
+
# Expand logical font asset path.
|
245
|
+
def font_path(path)
|
246
|
+
asset_path(path, type: :font)
|
247
|
+
end
|
248
|
+
|
249
|
+
# Expand logical javascript asset path.
|
250
|
+
def javascript_path(path)
|
251
|
+
asset_path(path, type: :javascript)
|
252
|
+
end
|
253
|
+
|
254
|
+
# Expand logical stylesheet asset path.
|
255
|
+
def stylesheet_path(path)
|
256
|
+
asset_path(path, type: :stylesheet)
|
257
|
+
end
|
258
|
+
|
259
|
+
protected
|
260
|
+
|
261
|
+
# Returns a URI-encoded data URI (always "-quoted).
|
262
|
+
def svg_asset_data_uri(asset)
|
263
|
+
svg = asset.source.dup
|
264
|
+
optimize_svg_for_uri_escaping!(svg)
|
265
|
+
data = Rack::Utils.escape(svg)
|
266
|
+
optimize_quoted_uri_escapes!(data)
|
267
|
+
"\"data:#{asset.content_type};charset=utf-8,#{data}\""
|
268
|
+
end
|
269
|
+
|
270
|
+
# Returns a Base64-encoded data URI.
|
271
|
+
def base64_asset_data_uri(asset)
|
272
|
+
data = Rack::Utils.escape(EncodingUtils.base64(asset.source))
|
273
|
+
"data:#{asset.content_type};base64,#{data}"
|
274
|
+
end
|
275
|
+
|
276
|
+
# Optimizes an SVG for being URI-escaped.
|
277
|
+
#
|
278
|
+
# This method only performs these basic but crucial optimizations:
|
279
|
+
# * Replaces " with ', because ' does not need escaping.
|
280
|
+
# * Removes comments, meta, doctype, and newlines.
|
281
|
+
# * Collapses whitespace.
|
282
|
+
def optimize_svg_for_uri_escaping!(svg)
|
283
|
+
# Remove comments, xml meta, and doctype
|
284
|
+
svg.gsub!(/<!--.*?-->|<\?.*?\?>|<!.*?>/m, '')
|
285
|
+
# Replace consecutive whitespace and newlines with a space
|
286
|
+
svg.gsub!(/\s+/, ' ')
|
287
|
+
# Collapse inter-tag whitespace
|
288
|
+
svg.gsub!('> <', '><')
|
289
|
+
# Replace " with '
|
290
|
+
svg.gsub!(/([\w:])="(.*?)"/, "\\1='\\2'")
|
291
|
+
svg.strip!
|
292
|
+
end
|
293
|
+
|
294
|
+
# Un-escapes characters in the given URI-escaped string that do not need
|
295
|
+
# escaping in "-quoted data URIs.
|
296
|
+
def optimize_quoted_uri_escapes!(escaped)
|
297
|
+
escaped.gsub!('%3D', '=')
|
298
|
+
escaped.gsub!('%3A', ':')
|
299
|
+
escaped.gsub!('%2F', '/')
|
300
|
+
escaped.gsub!('%27', "'")
|
301
|
+
escaped.tr!('+', ' ')
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|