hanami-assets 1.3.5 → 2.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -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 +98 -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,98 @@
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_run_command, default: "npm run --silent"
22
+
23
+ # @since 2.1.0
24
+ # @api private
25
+ setting :path_prefix, default: "/assets"
26
+
27
+ # @since 2.1.0
28
+ # @api private
29
+ setting :destination
30
+
31
+ # @since 2.1.0
32
+ # @api private
33
+ setting :subresource_integrity, default: []
34
+
35
+ # @since 2.1.0
36
+ # @api private
37
+ setting :sources, default: [], constructor: -> v { Array(v).flatten }
38
+
39
+ # @since 2.1.0
40
+ # @api private
41
+ setting :base_url, constructor: -> url { BaseUrl.new(url.to_s) }
42
+
43
+ # @since 2.1.0
44
+ # @api private
45
+ setting :entry_points_pattern, default: "index.{js,jsx,ts,tsx}"
46
+
47
+ # @since 2.1.0
48
+ # @api private
49
+ setting :manifest_path
50
+
51
+ # @since 2.1.0
52
+ # @api private
53
+ def initialize(**values)
54
+ super()
55
+
56
+ config.update(values.select { |k| _settings.key?(k) })
57
+
58
+ yield(config) if block_given?
59
+ end
60
+
61
+ # @since 2.1.0
62
+ # @api private
63
+ def entry_points
64
+ sources.map do |source|
65
+ Dir.glob(File.join(source, "**", config.entry_points_pattern))
66
+ end.flatten
67
+ end
68
+
69
+ # Check if the given source is linked via Cross-Origin policy.
70
+ # In other words, the given source, doesn't satisfy the Same-Origin policy.
71
+ #
72
+ # @see https://en.wikipedia.org/wiki/Same-origin_policy#Origin_determination_rules
73
+ # @see https://en.wikipedia.org/wiki/Same-origin_policy#document.domain_property
74
+ #
75
+ # @since 1.2.0
76
+ # @api private
77
+ def crossorigin?(source)
78
+ base_url.crossorigin?(source)
79
+ end
80
+
81
+ private
82
+
83
+ # @api private
84
+ def method_missing(name, ...)
85
+ if config.respond_to?(name)
86
+ config.public_send(name, ...)
87
+ else
88
+ super
89
+ end
90
+ end
91
+
92
+ # @api private
93
+ def respond_to_missing?(name, _incude_all = false)
94
+ config.respond_to?(name) || super
95
+ end
96
+ end
97
+ end
98
+ 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.rc1"
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"