hanami-assets 1.3.4 → 2.1.0.beta2

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 +4 -4
  2. data/CHANGELOG.md +27 -0
  3. data/LICENSE.md +1 -1
  4. data/README.md +92 -315
  5. data/bin/hanami-assets +7 -6
  6. data/hanami-assets.gemspec +23 -28
  7. data/lib/hanami/assets/asset.rb +83 -0
  8. data/lib/hanami/assets/base_url.rb +64 -0
  9. data/lib/hanami/assets/config.rb +106 -0
  10. data/lib/hanami/assets/errors.rb +46 -0
  11. data/lib/hanami/assets/version.rb +4 -2
  12. data/lib/hanami/assets.rb +63 -143
  13. data/lib/hanami-assets.rb +3 -0
  14. metadata +36 -104
  15. data/lib/hanami/assets/bundler/asset.rb +0 -98
  16. data/lib/hanami/assets/bundler/compressor.rb +0 -61
  17. data/lib/hanami/assets/bundler/manifest_entry.rb +0 -62
  18. data/lib/hanami/assets/bundler.rb +0 -152
  19. data/lib/hanami/assets/cache.rb +0 -100
  20. data/lib/hanami/assets/compiler.rb +0 -285
  21. data/lib/hanami/assets/compilers/less.rb +0 -29
  22. data/lib/hanami/assets/compilers/sass.rb +0 -59
  23. data/lib/hanami/assets/compressors/abstract.rb +0 -119
  24. data/lib/hanami/assets/compressors/builtin_javascript.rb +0 -36
  25. data/lib/hanami/assets/compressors/builtin_stylesheet.rb +0 -57
  26. data/lib/hanami/assets/compressors/closure_javascript.rb +0 -25
  27. data/lib/hanami/assets/compressors/javascript.rb +0 -77
  28. data/lib/hanami/assets/compressors/jsmin.rb +0 -284
  29. data/lib/hanami/assets/compressors/null_compressor.rb +0 -19
  30. data/lib/hanami/assets/compressors/sass_stylesheet.rb +0 -36
  31. data/lib/hanami/assets/compressors/stylesheet.rb +0 -77
  32. data/lib/hanami/assets/compressors/uglifier_javascript.rb +0 -25
  33. data/lib/hanami/assets/compressors/yui_javascript.rb +0 -25
  34. data/lib/hanami/assets/compressors/yui_stylesheet.rb +0 -25
  35. data/lib/hanami/assets/config/global_sources.rb +0 -50
  36. data/lib/hanami/assets/config/manifest.rb +0 -140
  37. data/lib/hanami/assets/config/sources.rb +0 -78
  38. data/lib/hanami/assets/configuration.rb +0 -652
  39. data/lib/hanami/assets/helpers.rb +0 -935
  40. data/lib/hanami/assets/precompiler.rb +0 -95
@@ -1,38 +1,33 @@
1
- # coding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
- lib = File.expand_path('../lib', __FILE__)
3
+ lib = File.expand_path("../lib", __FILE__)
4
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
- require 'hanami/assets/version'
5
+ require "hanami/assets/version"
6
6
 
7
7
  Gem::Specification.new do |spec|
8
- spec.name = 'hanami-assets'
8
+ spec.name = "hanami-assets"
9
9
  spec.version = Hanami::Assets::VERSION
10
- spec.authors = ['Luca Guidi']
11
- spec.email = ['me@lucaguidi.com']
12
- spec.summary = 'Assets management'
13
- spec.description = 'Assets management for Ruby web applications'
14
- spec.homepage = 'http://hanamirb.org'
15
- spec.license = 'MIT'
10
+ spec.authors = ["Luca Guidi"]
11
+ spec.email = ["me@lucaguidi.com"]
12
+ spec.summary = "Assets management"
13
+ spec.description = "Assets management for Ruby web applications"
14
+ spec.homepage = "http://hanamirb.org"
15
+ spec.license = "MIT"
16
16
 
17
- spec.files = `git ls-files -- lib/* bin/* CHANGELOG.md LICENSE.md README.md hanami-assets.gemspec`.split($/) # rubocop:disable Style/SpecialGlobalVars
17
+ spec.files = `git ls-files -- lib/* bin/* CHANGELOG.md LICENSE.md README.md hanami-assets.gemspec`.split($/)
18
18
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
- spec.require_paths = ['lib']
21
- spec.required_ruby_version = '>= 2.3.0'
19
+ spec.require_paths = ["lib"]
20
+ spec.metadata["rubygems_mfa_required"] = "true"
21
+ spec.required_ruby_version = ">= 3.0"
22
22
 
23
- spec.add_runtime_dependency 'hanami-utils', '~> 1.3'
24
- spec.add_runtime_dependency 'hanami-helpers', '~> 1.3'
25
- spec.add_runtime_dependency 'tilt', '~> 2.0', '>= 2.0.2'
23
+ spec.add_runtime_dependency "zeitwerk", "~> 2.6"
26
24
 
27
- spec.add_development_dependency 'bundler', '>= 1.6', '< 3'
28
- spec.add_development_dependency 'rake', '~> 12.0'
29
- spec.add_development_dependency 'rspec', '~> 3.7'
30
-
31
- spec.add_development_dependency 'yui-compressor', '~> 0.12'
32
- spec.add_development_dependency 'uglifier', '~> 2.7'
33
- spec.add_development_dependency 'closure-compiler', '~> 1.1'
34
- spec.add_development_dependency 'sassc', '~> 2.0'
35
-
36
- spec.add_development_dependency 'coffee-script', '~> 2.3'
37
- spec.add_development_dependency 'babel-transpiler', '~> 0.7'
25
+ spec.add_development_dependency "bundler", ">= 1.6", "< 3"
26
+ spec.add_development_dependency "rake", "~> 13"
27
+ spec.add_development_dependency "rspec", "~> 3.9"
28
+ spec.add_development_dependency "rubocop", "~> 1.0"
29
+ spec.add_development_dependency "rack", "~> 2.2"
30
+ spec.add_development_dependency "rack-test", "~> 1.1"
31
+ spec.add_development_dependency "dry-configurable", "~> 1.1"
32
+ spec.add_development_dependency "dry-inflector", "~> 1.0"
38
33
  end
@@ -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,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hanami
2
- module Assets
4
+ class Assets
3
5
  # Defines the version
4
6
  #
5
7
  # @since 0.1.0
6
- VERSION = '1.3.4'.freeze
8
+ VERSION = "2.1.0.beta2"
7
9
  end
8
10
  end
data/lib/hanami/assets.rb CHANGED
@@ -1,4 +1,7 @@
1
- require 'hanami/utils/class_attribute'
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "zeitwerk"
2
5
 
3
6
  # Hanami
4
7
  #
@@ -7,167 +10,84 @@ module Hanami
7
10
  # Assets management for Ruby web applications
8
11
  #
9
12
  # @since 0.1.0
10
- module Assets
11
- # Base error for Hanami::Assets
12
- #
13
- # All the errors defined in this framework MUST inherit from it.
14
- #
15
- # @since 0.1.0
16
- 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
17
28
  end
18
29
 
19
- require 'hanami/assets/version'
20
- require 'hanami/assets/configuration'
21
- require 'hanami/assets/config/global_sources'
22
- require 'hanami/assets/helpers'
30
+ gem_loader.setup
31
+ require_relative "assets/version"
32
+ require_relative "assets/errors"
23
33
 
24
- include Utils::ClassAttribute
25
-
26
- # Configuration
27
- #
28
- # @since 0.1.0
34
+ # @since 2.1.0
29
35
  # @api private
30
- class_attribute :configuration
31
- self.configuration = Configuration.new
36
+ SEPARATOR = "/"
37
+ private_constant :SEPARATOR
32
38
 
33
- # Configure framework
34
- #
35
- # @param blk [Proc] configuration code block
36
- #
37
- # @return self
38
- #
39
- # @since 0.1.0
40
- #
41
- # @see Hanami::Assets::Configuration
42
- def self.configure(&blk)
43
- configuration.instance_eval(&blk)
44
- self
45
- end
39
+ attr_reader :config
46
40
 
47
- # Prepare assets for deploys
48
- #
49
- # @since 0.1.0
50
- def self.deploy
51
- require 'hanami/assets/precompiler'
52
- require 'hanami/assets/bundler'
53
-
54
- Precompiler.new(configuration, duplicates).run
55
- Bundler.new(configuration, duplicates).run
41
+ # @since 2.1.0
42
+ # @api public
43
+ def initialize(config:)
44
+ @config = config
56
45
  end
57
46
 
58
- # Precompile assets
59
- #
60
- # @since 0.4.0
61
- def self.precompile(configurations)
62
- require 'hanami/assets/precompiler'
63
- require 'hanami/assets/bundler'
64
-
65
- Precompiler.new(configuration, configurations).run
66
- 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
+ )
67
63
  end
68
64
 
69
- # Preload the framework
70
- #
71
- # This MUST be used in production mode
72
- #
73
- # @since 0.1.0
74
- #
75
- # @example Direct Invocation
76
- # require 'hanami/assets'
77
- #
78
- # Hanami::Assets.load!
79
- #
80
- # @example Load Via Configuration Block
81
- # require 'hanami/assets'
82
- #
83
- # Hanami::Assets.configure do
84
- # # ...
85
- # end.load!
86
- def self.load!
87
- configuration.load!
65
+ # @since 2.1.0
66
+ # @api public
67
+ def subresource_integrity?
68
+ config.subresource_integrity.any?
88
69
  end
89
70
 
90
- # Global assets sources
91
- #
92
- # This is designed for third party integration gems with frontend frameworks
93
- # like Bootstrap, Ember.js or React.
94
- #
95
- # Developers can maintain gems that ship static assets for these frameworks
96
- # and make them available to Hanami::Assets.
97
- #
98
- # @return [Hanami::Assets::Config::GlobalSources]
99
- #
100
- # @since 0.1.0
101
- #
102
- # @example Ember.js Integration
103
- # # lib/hanami/emberjs.rb (third party gem)
104
- # require 'hanami/assets'
105
- #
106
- # Hanami::Assets.sources << '/path/to/emberjs/assets'
107
- def self.sources
108
- synchronize do
109
- @@sources ||= Config::GlobalSources.new # rubocop:disable Style/ClassVars
110
- end
71
+ # @since 2.1.0
72
+ # @api public
73
+ def crossorigin?(source_path)
74
+ config.crossorigin?(source_path)
111
75
  end
112
76
 
113
- # Duplicate the framework and generate modules for the target application
114
- #
115
- # @param _mod [Module] the Ruby namespace of the application
116
- # @param blk [Proc] an optional block to configure the framework
117
- #
118
- # @return [Module] a copy of Hanami::Assets
119
- #
120
- # @since 0.1.0
121
- #
122
- # @see Hanami::Assets#dupe
123
- # @see Hanami::Assets::Configuration
124
- def self.duplicate(_mod, &blk)
125
- dupe.tap do |duplicated|
126
- duplicated.configure(&blk) if block_given?
127
- duplicates << duplicated
128
- end
129
- end
77
+ private
130
78
 
131
- # Duplicate Hanami::Assets in order to create a new separated instance
132
- # of the framework.
133
- #
134
- # The new instance of the framework will be completely decoupled from the
135
- # original. It will inherit the configuration, but all the changes that
136
- # happen after the duplication, won't be reflected on the other copies.
137
- #
138
- # @return [Module] a copy of Hanami::Assets
139
- #
140
- # @since 0.1.0
141
- # @api private
142
- def self.dupe
143
- dup.tap do |duplicated|
144
- duplicated.configuration = configuration.duplicate
145
- end
146
- end
79
+ def manifest
80
+ return @manifest if instance_variable_defined?(:@manifest)
147
81
 
148
- # Keep track of duplicated frameworks
149
- #
150
- # @return [Array] a collection of duplicated frameworks
151
- #
152
- # @since 0.1.0
153
- # @api private
154
- #
155
- # @see Hanami::Assets#duplicate
156
- # @see Hanami::Assets#dupe
157
- def self.duplicates
158
- synchronize do
159
- @@duplicates ||= [] # rubocop:disable Style/ClassVars
82
+ unless config.manifest_path
83
+ raise ConfigError, "no manifest_path configured"
160
84
  end
161
- end
162
85
 
163
- class << self
164
- private
165
-
166
- # @since 0.1.0
167
- # @api private
168
- def synchronize(&blk)
169
- Mutex.new.synchronize(&blk)
86
+ unless File.exist?(config.manifest_path)
87
+ raise ManifestMissingError.new(config.manifest_path)
170
88
  end
89
+
90
+ @manifest = JSON.parse(File.read(config.manifest_path))
171
91
  end
172
92
  end
173
93
  end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "hanami/assets"