hanami-assets 1.3.5 → 2.1.0.beta2

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/README.md +92 -314
  4. data/hanami-assets.gemspec +26 -33
  5. data/lib/hanami/assets/asset.rb +83 -0
  6. data/lib/hanami/assets/base_url.rb +64 -0
  7. data/lib/hanami/assets/config.rb +106 -0
  8. data/lib/hanami/assets/errors.rb +46 -0
  9. data/lib/hanami/assets/version.rb +2 -2
  10. data/lib/hanami/assets.rb +61 -143
  11. data/lib/hanami-assets.rb +3 -0
  12. metadata +33 -115
  13. data/lib/hanami/assets/bundler/asset.rb +0 -100
  14. data/lib/hanami/assets/bundler/compressor.rb +0 -63
  15. data/lib/hanami/assets/bundler/manifest_entry.rb +0 -64
  16. data/lib/hanami/assets/bundler.rb +0 -154
  17. data/lib/hanami/assets/cache.rb +0 -102
  18. data/lib/hanami/assets/compiler.rb +0 -287
  19. data/lib/hanami/assets/compilers/less.rb +0 -31
  20. data/lib/hanami/assets/compilers/sass.rb +0 -61
  21. data/lib/hanami/assets/compressors/abstract.rb +0 -119
  22. data/lib/hanami/assets/compressors/builtin_javascript.rb +0 -36
  23. data/lib/hanami/assets/compressors/builtin_stylesheet.rb +0 -57
  24. data/lib/hanami/assets/compressors/closure_javascript.rb +0 -25
  25. data/lib/hanami/assets/compressors/javascript.rb +0 -77
  26. data/lib/hanami/assets/compressors/jsmin.rb +0 -284
  27. data/lib/hanami/assets/compressors/null_compressor.rb +0 -19
  28. data/lib/hanami/assets/compressors/sass_stylesheet.rb +0 -36
  29. data/lib/hanami/assets/compressors/stylesheet.rb +0 -77
  30. data/lib/hanami/assets/compressors/uglifier_javascript.rb +0 -25
  31. data/lib/hanami/assets/compressors/yui_javascript.rb +0 -25
  32. data/lib/hanami/assets/compressors/yui_stylesheet.rb +0 -25
  33. data/lib/hanami/assets/config/global_sources.rb +0 -52
  34. data/lib/hanami/assets/config/manifest.rb +0 -142
  35. data/lib/hanami/assets/config/sources.rb +0 -80
  36. data/lib/hanami/assets/configuration.rb +0 -657
  37. data/lib/hanami/assets/helpers.rb +0 -945
  38. data/lib/hanami/assets/precompiler.rb +0 -97
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ class Assets
5
+ # Represents a single front end asset.
6
+ #
7
+ # @api public
8
+ # @since 2.1.0
9
+ class Asset
10
+ # @api private
11
+ # @since 2.1.0
12
+ attr_reader :config
13
+ private :config
14
+
15
+ # Returns the asset's absolute URL path.
16
+ #
17
+ # @example Asset from local dev server
18
+ # asset.path # => "/assets/app.js"
19
+ #
20
+ # @example Deployed asset with fingerprinted name
21
+ # asset.path # => "/assets/app-28a6b886de2372ee3922fcaf3f78f2d8.js"
22
+ #
23
+ # @return [String]
24
+ #
25
+ # @api public
26
+ # @since 2.1.0
27
+ attr_reader :path
28
+
29
+ # @api private
30
+ # @since 2.1.0
31
+ attr_reader :base_url
32
+ private :base_url
33
+
34
+ # Returns the asset's subresource integrity value, or nil if none is available.
35
+ #
36
+ # @return [String, nil]
37
+ #
38
+ # @api public
39
+ # @since 2.1.0
40
+ attr_reader :sri
41
+
42
+ # @api private
43
+ # @since 2.1.0
44
+ def initialize(path:, base_url:, sri: nil)
45
+ @path = path
46
+ @base_url = base_url
47
+ @sri = sri
48
+ end
49
+
50
+ # @api public
51
+ # @since 2.1.0
52
+ alias_method :subresource_integrity_value, :sri
53
+
54
+ # Returns the asset's full URL.
55
+ #
56
+ # @example Asset from local dev server
57
+ # asset.path # => "https://example.com/assets/app.js"
58
+ #
59
+ # @example Deployed asset with fingerprinted name
60
+ # asset.path # => "https://example.com/assets/app-28a6b886de2372ee3922fcaf3f78f2d8.js"
61
+ #
62
+ # @return [String]
63
+ #
64
+ # @api public
65
+ # @since 2.1.0
66
+ def url
67
+ base_url.join(path)
68
+ end
69
+
70
+ # Returns the asset's full URL
71
+ #
72
+ # @return [String]
73
+ #
74
+ # @see #url
75
+ #
76
+ # @api public
77
+ # @since 2.1.0
78
+ def to_s
79
+ url
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ class Assets
5
+ # Base URL
6
+ #
7
+ # @since 2.1.0
8
+ # @api private
9
+ class BaseUrl
10
+ # @since 2.1.0
11
+ # @api private
12
+ SEPARATOR = "/"
13
+ private_constant :SEPARATOR
14
+
15
+ # @since 2.1.0
16
+ # @api private
17
+ attr_reader :url
18
+ private :url
19
+
20
+ # Initialize a base URL
21
+ #
22
+ # @param url [String] the URL
23
+ # @param prefix [String,NilClass] the prefix
24
+ #
25
+ # @since 2.1.0
26
+ # @api private
27
+ def initialize(url, prefix = nil)
28
+ @url = URI(url + prefix.to_s).to_s
29
+ freeze
30
+ end
31
+
32
+ # Join the base URL with the given paths
33
+ #
34
+ # @param other [String] the paths
35
+ # @return [String] the joined URL
36
+ #
37
+ # @since 2.1.0
38
+ # @api private
39
+ def join(other)
40
+ (url + other).to_s
41
+ end
42
+
43
+ # @since 2.1.0
44
+ # @api private
45
+ def to_s
46
+ @url
47
+ end
48
+
49
+ # Check if the source is a cross origin
50
+ #
51
+ # @param source [String] the source
52
+ # @return [Boolean] true if the source is a cross origin
53
+ #
54
+ # @since 2.1.0
55
+ # @api private
56
+ def crossorigin?(source)
57
+ # TODO: review if this is the right way to check for cross origin
58
+ return true if @url.empty?
59
+
60
+ !source.start_with?(@url)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/configurable"
4
+ require_relative "base_url"
5
+
6
+ module Hanami
7
+ class Assets
8
+ # Framework configuration
9
+ #
10
+ # @since 0.1.0
11
+ class Config
12
+ include Dry::Configurable
13
+
14
+ # @since 2.1.0
15
+ # @api private
16
+ BASE_URL = ""
17
+ private_constant :BASE_URL
18
+
19
+ # @since 2.1.0
20
+ # @api private
21
+ setting :package_manager_executable, default: "npm"
22
+
23
+ # @since 2.1.0
24
+ # @api private
25
+ setting :package_manager_command, default: "exec"
26
+
27
+ # @since 2.1.0
28
+ # @api private
29
+ setting :executable, default: "hanami-assets"
30
+
31
+ # @since 2.1.0
32
+ # @api private
33
+ setting :path_prefix, default: "/assets"
34
+
35
+ # @since 2.1.0
36
+ # @api private
37
+ setting :destination
38
+
39
+ # @since 2.1.0
40
+ # @api private
41
+ setting :subresource_integrity, default: []
42
+
43
+ # @since 2.1.0
44
+ # @api private
45
+ setting :sources, default: [], constructor: -> v { Array(v).flatten }
46
+
47
+ # @since 2.1.0
48
+ # @api private
49
+ setting :base_url, constructor: -> url { BaseUrl.new(url.to_s) }
50
+
51
+ # @since 2.1.0
52
+ # @api private
53
+ setting :entry_points_pattern, default: "index.{js,jsx,ts,tsx}"
54
+
55
+ # @since 2.1.0
56
+ # @api private
57
+ setting :manifest_path
58
+
59
+ # @since 2.1.0
60
+ # @api private
61
+ def initialize(**values)
62
+ super()
63
+
64
+ config.update(values.select { |k| _settings.key?(k) })
65
+
66
+ yield(config) if block_given?
67
+ end
68
+
69
+ # @since 2.1.0
70
+ # @api private
71
+ def entry_points
72
+ sources.map do |source|
73
+ Dir.glob(File.join(source, "**", config.entry_points_pattern))
74
+ end.flatten
75
+ end
76
+
77
+ # Check if the given source is linked via Cross-Origin policy.
78
+ # In other words, the given source, doesn't satisfy the Same-Origin policy.
79
+ #
80
+ # @see https://en.wikipedia.org/wiki/Same-origin_policy#Origin_determination_rules
81
+ # @see https://en.wikipedia.org/wiki/Same-origin_policy#document.domain_property
82
+ #
83
+ # @since 1.2.0
84
+ # @api private
85
+ def crossorigin?(source)
86
+ base_url.crossorigin?(source)
87
+ end
88
+
89
+ private
90
+
91
+ # @api private
92
+ def method_missing(name, ...)
93
+ if config.respond_to?(name)
94
+ config.public_send(name, ...)
95
+ else
96
+ super
97
+ end
98
+ end
99
+
100
+ # @api private
101
+ def respond_to_missing?(name, _incude_all = false)
102
+ config.respond_to?(name) || super
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ class Assets
5
+ # Base error for Hanami::Assets
6
+ #
7
+ # All the errors defined in this framework MUST inherit from it.
8
+ #
9
+ # @since 0.1.0
10
+ class Error < ::StandardError
11
+ end
12
+
13
+ # Error raised when assets config is not valid.
14
+ #
15
+ # @since 2.1.0
16
+ # @api public
17
+ class ConfigError < Error
18
+ end
19
+
20
+ # Error returned when the assets manifest file is missing.
21
+ #
22
+ # @since 2.1.0
23
+ # @api public
24
+ class ManifestMissingError < Error
25
+ def initialize(manifest_path)
26
+ super(<<~TEXT)
27
+ Missing manifest file at #{manifest_path.inspect}
28
+
29
+ Have you run `hanami assets compile` or `hanami assets watch`?
30
+ TEXT
31
+ end
32
+ end
33
+
34
+ # Error raised when no asset can be found for a source path.
35
+ #
36
+ # @since 2.1.0
37
+ # @api public
38
+ class AssetMissingError < Error
39
+ def initialize(source_path)
40
+ super(<<~TEXT)
41
+ No asset found for #{source_path.inspect}
42
+ TEXT
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Hanami
4
- module Assets
4
+ class Assets
5
5
  # Defines the version
6
6
  #
7
7
  # @since 0.1.0
8
- VERSION = "1.3.5"
8
+ VERSION = "2.1.0.beta2"
9
9
  end
10
10
  end
data/lib/hanami/assets.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "hanami/utils/class_attribute"
3
+ require "json"
4
+ require "zeitwerk"
4
5
 
5
6
  # Hanami
6
7
  #
@@ -9,167 +10,84 @@ module Hanami
9
10
  # Assets management for Ruby web applications
10
11
  #
11
12
  # @since 0.1.0
12
- module Assets
13
- # Base error for Hanami::Assets
14
- #
15
- # All the errors defined in this framework MUST inherit from it.
16
- #
17
- # @since 0.1.0
18
- class Error < ::StandardError
13
+ class Assets
14
+ # @since 2.1.0
15
+ # @api private
16
+ def self.gem_loader
17
+ @gem_loader ||= Zeitwerk::Loader.new.tap do |loader|
18
+ root = File.expand_path("..", __dir__)
19
+ loader.tag = "hanami-assets"
20
+ loader.push_dir(root)
21
+ loader.ignore(
22
+ "#{root}/hanami-assets.rb",
23
+ "#{root}/hanami/assets/version.rb",
24
+ "#{root}/hanami/assets/errors.rb"
25
+ )
26
+ loader.inflector = Zeitwerk::GemInflector.new("#{root}/hanami-assets.rb")
27
+ end
19
28
  end
20
29
 
21
- require "hanami/assets/version"
22
- require "hanami/assets/configuration"
23
- require "hanami/assets/config/global_sources"
24
- require "hanami/assets/helpers"
30
+ gem_loader.setup
31
+ require_relative "assets/version"
32
+ require_relative "assets/errors"
25
33
 
26
- include Utils::ClassAttribute
27
-
28
- # Configuration
29
- #
30
- # @since 0.1.0
34
+ # @since 2.1.0
31
35
  # @api private
32
- class_attribute :configuration
33
- self.configuration = Configuration.new
34
-
35
- # Configure framework
36
- #
37
- # @param blk [Proc] configuration code block
38
- #
39
- # @return self
40
- #
41
- # @since 0.1.0
42
- #
43
- # @see Hanami::Assets::Configuration
44
- def self.configure(&blk)
45
- configuration.instance_eval(&blk)
46
- self
47
- end
36
+ SEPARATOR = "/"
37
+ private_constant :SEPARATOR
48
38
 
49
- # Prepare assets for deploys
50
- #
51
- # @since 0.1.0
52
- def self.deploy
53
- require "hanami/assets/precompiler"
54
- require "hanami/assets/bundler"
39
+ attr_reader :config
55
40
 
56
- Precompiler.new(configuration, duplicates).run
57
- Bundler.new(configuration, duplicates).run
41
+ # @since 2.1.0
42
+ # @api public
43
+ def initialize(config:)
44
+ @config = config
58
45
  end
59
46
 
60
- # Precompile assets
61
- #
62
- # @since 0.4.0
63
- def self.precompile(configurations)
64
- require "hanami/assets/precompiler"
65
- require "hanami/assets/bundler"
66
-
67
- Precompiler.new(configuration, configurations).run
68
- Bundler.new(configuration, configurations).run
47
+ # @since 2.1.0
48
+ # @api public
49
+ def [](path)
50
+ asset_attrs = manifest
51
+ .fetch(path) { raise AssetMissingError.new(path) }
52
+ .transform_keys(&:to_sym)
53
+ .tap { |attrs|
54
+ # The `url` attribute we receive from the manifest is actually a path; rename it as such
55
+ # so our `Asset` attributes make more sense on their own.
56
+ attrs[:path] = attrs.delete(:url)
57
+ }
58
+
59
+ Asset.new(
60
+ **asset_attrs,
61
+ base_url: config.base_url
62
+ )
69
63
  end
70
64
 
71
- # Preload the framework
72
- #
73
- # This MUST be used in production mode
74
- #
75
- # @since 0.1.0
76
- #
77
- # @example Direct Invocation
78
- # require 'hanami/assets'
79
- #
80
- # Hanami::Assets.load!
81
- #
82
- # @example Load Via Configuration Block
83
- # require 'hanami/assets'
84
- #
85
- # Hanami::Assets.configure do
86
- # # ...
87
- # end.load!
88
- def self.load!
89
- configuration.load!
65
+ # @since 2.1.0
66
+ # @api public
67
+ def subresource_integrity?
68
+ config.subresource_integrity.any?
90
69
  end
91
70
 
92
- # Global assets sources
93
- #
94
- # This is designed for third party integration gems with frontend frameworks
95
- # like Bootstrap, Ember.js or React.
96
- #
97
- # Developers can maintain gems that ship static assets for these frameworks
98
- # and make them available to Hanami::Assets.
99
- #
100
- # @return [Hanami::Assets::Config::GlobalSources]
101
- #
102
- # @since 0.1.0
103
- #
104
- # @example Ember.js Integration
105
- # # lib/hanami/emberjs.rb (third party gem)
106
- # require 'hanami/assets'
107
- #
108
- # Hanami::Assets.sources << '/path/to/emberjs/assets'
109
- def self.sources
110
- synchronize do
111
- @@sources ||= Config::GlobalSources.new # rubocop:disable Style/ClassVars
112
- end
71
+ # @since 2.1.0
72
+ # @api public
73
+ def crossorigin?(source_path)
74
+ config.crossorigin?(source_path)
113
75
  end
114
76
 
115
- # Duplicate the framework and generate modules for the target application
116
- #
117
- # @param _mod [Module] the Ruby namespace of the application
118
- # @param blk [Proc] an optional block to configure the framework
119
- #
120
- # @return [Module] a copy of Hanami::Assets
121
- #
122
- # @since 0.1.0
123
- #
124
- # @see Hanami::Assets#dupe
125
- # @see Hanami::Assets::Configuration
126
- def self.duplicate(_mod, &blk)
127
- dupe.tap do |duplicated|
128
- duplicated.configure(&blk) if block_given?
129
- duplicates << duplicated
130
- end
131
- end
77
+ private
132
78
 
133
- # Duplicate Hanami::Assets in order to create a new separated instance
134
- # of the framework.
135
- #
136
- # The new instance of the framework will be completely decoupled from the
137
- # original. It will inherit the configuration, but all the changes that
138
- # happen after the duplication, won't be reflected on the other copies.
139
- #
140
- # @return [Module] a copy of Hanami::Assets
141
- #
142
- # @since 0.1.0
143
- # @api private
144
- def self.dupe
145
- dup.tap do |duplicated|
146
- duplicated.configuration = configuration.duplicate
147
- end
148
- end
79
+ def manifest
80
+ return @manifest if instance_variable_defined?(:@manifest)
149
81
 
150
- # Keep track of duplicated frameworks
151
- #
152
- # @return [Array] a collection of duplicated frameworks
153
- #
154
- # @since 0.1.0
155
- # @api private
156
- #
157
- # @see Hanami::Assets#duplicate
158
- # @see Hanami::Assets#dupe
159
- def self.duplicates
160
- synchronize do
161
- @@duplicates ||= [] # rubocop:disable Style/ClassVars
82
+ unless config.manifest_path
83
+ raise ConfigError, "no manifest_path configured"
162
84
  end
163
- end
164
85
 
165
- class << self
166
- private
167
-
168
- # @since 0.1.0
169
- # @api private
170
- def synchronize(&blk)
171
- Mutex.new.synchronize(&blk)
86
+ unless File.exist?(config.manifest_path)
87
+ raise ManifestMissingError.new(config.manifest_path)
172
88
  end
89
+
90
+ @manifest = JSON.parse(File.read(config.manifest_path))
173
91
  end
174
92
  end
175
93
  end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "hanami/assets"