proscenium 0.9.1-x86_64-linux → 0.11.0.pre.1-x86_64-linux

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +423 -63
  3. data/lib/proscenium/builder.rb +126 -0
  4. data/lib/proscenium/css_module/path.rb +31 -0
  5. data/lib/proscenium/css_module/transformer.rb +76 -0
  6. data/lib/proscenium/css_module.rb +6 -28
  7. data/lib/proscenium/ensure_loaded.rb +27 -0
  8. data/lib/proscenium/ext/proscenium +0 -0
  9. data/lib/proscenium/ext/proscenium.h +19 -12
  10. data/lib/proscenium/helper.rb +62 -0
  11. data/lib/proscenium/importer.rb +110 -0
  12. data/lib/proscenium/libs/react-manager/index.jsx +88 -0
  13. data/lib/proscenium/libs/react-manager/react.js +2 -0
  14. data/lib/proscenium/libs/stimulus-loading.js +83 -0
  15. data/lib/proscenium/log_subscriber.rb +1 -2
  16. data/lib/proscenium/middleware/base.rb +1 -1
  17. data/lib/proscenium/middleware/esbuild.rb +3 -5
  18. data/lib/proscenium/middleware.rb +7 -1
  19. data/lib/proscenium/{side_load/monkey.rb → monkey.rb} +16 -12
  20. data/lib/proscenium/phlex/{resolve_css_modules.rb → css_modules.rb} +6 -20
  21. data/lib/proscenium/phlex/page.rb +2 -2
  22. data/lib/proscenium/phlex/react_component.rb +27 -64
  23. data/lib/proscenium/phlex.rb +10 -29
  24. data/lib/proscenium/railtie.rb +20 -22
  25. data/lib/proscenium/react_componentable.rb +94 -0
  26. data/lib/proscenium/resolver.rb +37 -0
  27. data/lib/proscenium/side_load.rb +13 -72
  28. data/lib/proscenium/source_path.rb +15 -0
  29. data/lib/proscenium/utils.rb +13 -0
  30. data/lib/proscenium/version.rb +1 -1
  31. data/lib/proscenium/view_component/css_modules.rb +11 -0
  32. data/lib/proscenium/view_component/react_component.rb +15 -28
  33. data/lib/proscenium/view_component/sideload.rb +4 -0
  34. data/lib/proscenium/view_component.rb +8 -31
  35. data/lib/proscenium.rb +24 -68
  36. metadata +21 -58
  37. data/lib/proscenium/css_module/class_names_resolver.rb +0 -66
  38. data/lib/proscenium/css_module/resolver.rb +0 -76
  39. data/lib/proscenium/current.rb +0 -9
  40. data/lib/proscenium/esbuild/golib.rb +0 -97
  41. data/lib/proscenium/esbuild.rb +0 -32
  42. data/lib/proscenium/phlex/component_concerns.rb +0 -27
  43. data/lib/proscenium/side_load/ensure_loaded.rb +0 -25
  44. data/lib/proscenium/side_load/helper.rb +0 -25
  45. data/lib/proscenium/view_component/tag_builder.rb +0 -23
@@ -2,83 +2,24 @@
2
2
 
3
3
  module Proscenium
4
4
  class SideLoad
5
- extend ActiveSupport::Autoload
6
-
7
- NotIncludedError = Class.new(StandardError)
8
-
9
- autoload :Monkey
10
- autoload :Helper
11
- autoload :EnsureLoaded
12
-
13
- EXTENSIONS = %i[js css].freeze
14
- EXTENSION_MAP = {
15
- '.css' => :css,
16
- # '.tsx' => :js,
17
- '.ts' => :js,
18
- # '.jsx' => :js,
19
- '.js' => :js
20
- }.freeze
21
-
22
- attr_reader :path
23
-
24
5
  class << self
25
- # Side load the given asset `path`, by appending it to `Proscenium::Current.loaded`, which is
26
- # a Set of 'js' and 'css' asset paths. This is idempotent, so side loading will never include
27
- # duplicates.
6
+ # Side loads the class, and its super classes that respond to `.source_path`.
28
7
  #
29
- # @return [Array] appended URL paths
30
- def append(path, extension_map = EXTENSION_MAP)
31
- new(path, extension_map).append
32
- end
33
-
34
- # Side load the given `path` at `type`, without first resolving the path. This still respects
35
- # idempotency of `Proscenium::Current.loaded`.
8
+ # Assign the `abstract_class` class variable to any abstract class, and it will not be side
9
+ # loaded. Additionally, if the class responds to `#sideload?`, and it returns false, it will
10
+ # not be side loaded.
36
11
  #
37
- # @param path [String]
38
- # @param type [Symbol] :js or :css
39
- def append!(path, type)
40
- return if Proscenium::Current.loaded[type].include?(path)
41
-
42
- Proscenium::Current.loaded[type] << log(path)
43
- end
44
-
45
- def log(value)
46
- ActiveSupport::Notifications.instrument('sideload.proscenium', identifier: value)
47
-
48
- value
49
- end
50
- end
51
-
52
- # @param path [Pathname, String] The path of the file to be side loaded.
53
- # @param extension_map [Hash] File extensions to side load.
54
- def initialize(path, extension_map = EXTENSION_MAP)
55
- @path = (path.is_a?(Pathname) ? path : Rails.root.join(path)).sub_ext('')
56
- @extension_map = extension_map
57
-
58
- Proscenium::Current.loaded ||= EXTENSIONS.index_with { |_e| Set.new }
59
- end
60
-
61
- def append
62
- @extension_map.filter_map do |ext, type|
63
- next unless (resolved_path = resolve_path(path.sub_ext(ext)))
64
-
65
- # Make sure path is not already side loaded.
66
- unless Proscenium::Current.loaded[type].include?(resolved_path)
67
- Proscenium::Current.loaded[type] << log(resolved_path)
12
+ # If the class responds to `.sideload`, it will be called instead of the regular side loading.
13
+ # You can use this to customise what is side loaded.
14
+ def sideload_inheritance_chain(obj)
15
+ return if !Proscenium.config.side_load || (obj.respond_to?(:sideload?) && !obj.sideload?)
16
+
17
+ klass = obj.class
18
+ while klass.respond_to?(:source_path) && klass.source_path && !klass.abstract_class
19
+ klass.respond_to?(:sideload) ? klass.sideload : Importer.sideload(klass.source_path)
20
+ klass = klass.superclass
68
21
  end
69
-
70
- resolved_path
71
22
  end
72
23
  end
73
-
74
- private
75
-
76
- def log(...)
77
- self.class.log(...)
78
- end
79
-
80
- def resolve_path(path)
81
- path.exist? ? Utils.resolve_path(path.to_s) : nil
82
- end
83
24
  end
84
25
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Include this into any class to expose a `source_path` class and instance method, which will return
4
+ # the absolute file system path to the current object.
5
+ module Proscenium::SourcePath
6
+ def self.included(base)
7
+ base.extend ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+ def source_path
12
+ @source_path ||= name.nil? ? nil : Pathname.new(const_source_location(name).first)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium
4
+ module Utils
5
+ module_function
6
+
7
+ # @param value [#to_s] The value to create the digest from. This will usually be a `Pathname`.
8
+ # @return [String] digest of the given value.
9
+ def digest(value)
10
+ Digest::SHA1.hexdigest(value.to_s)[..7]
11
+ end
12
+ end
13
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Proscenium
4
- VERSION = '0.9.1'
4
+ VERSION = '0.11.0.pre.1'
5
5
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium
4
+ module ViewComponent::CssModules
5
+ include Proscenium::CssModule
6
+
7
+ def self.included(base)
8
+ base.extend CssModule::Path
9
+ end
10
+ end
11
+ end
@@ -1,35 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- #
4
- # Renders HTML markup suitable for use with @proscenium/component-manager.
5
- #
6
- # If a content block is given, that content will be rendered inside the component, allowing for a
7
- # "loading" UI. If no block is given, then a loading text will be rendered.
8
- #
9
- # The parent div is not decorated with any attributes, apart from the selector class required by
10
- # component-manager. But if your component has a side loaded CSS module stylesheet
11
- # (component.module.css), with a `.component` class defined, then that class will be assigned to the
12
- # parent div as a CSS module.
13
- #
14
- class Proscenium::ViewComponent::ReactComponent < Proscenium::ViewComponent
15
- self.abstract_class = true
3
+ module Proscenium
4
+ # Renders a <div> for use with React components, with data attributes specifying the component
5
+ # path and props.
6
+ #
7
+ # If a content block is given, that content will be rendered inside the component, allowing for a
8
+ # "loading" UI. If no block is given, then a "loading..." text will be rendered. It is intended
9
+ # that the component is mounted to this div, and the loading UI will then be replaced with the
10
+ # component's rendered output.
11
+ class ViewComponent::ReactComponent < ViewComponent
12
+ self.abstract_class = true
16
13
 
17
- attr_accessor :props, :lazy
14
+ include ReactComponentable
18
15
 
19
- # @param props: [Hash]
20
- # @param lazy: [Boolean] Lazy load the component using IntersectionObserver. Default: true.
21
- # @param [Block]
22
- def initialize(props: {}, lazy: true)
23
- @props = props
24
- @lazy = lazy
25
-
26
- super
27
- end
28
-
29
- def call
30
- tag.div class: ['componentManagedByProscenium', css_module(:component)],
31
- data: { component: { path: virtual_path, props: props, lazy: lazy } } do
32
- tag.div content || 'loading...'
16
+ def call
17
+ tag.send root_tag, data: data_attributes do
18
+ tag.div content || 'loading...'
19
+ end
33
20
  end
34
21
  end
35
22
  end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium::ViewComponent::Sideload
4
+ end
@@ -4,52 +4,29 @@ require 'view_component'
4
4
 
5
5
  class Proscenium::ViewComponent < ViewComponent::Base
6
6
  extend ActiveSupport::Autoload
7
- include Proscenium::CssModule
8
7
 
9
- autoload :TagBuilder
8
+ autoload :Sideload
10
9
  autoload :ReactComponent
10
+ autoload :CssModules
11
+
12
+ include Proscenium::SourcePath
13
+ include CssModules
11
14
 
12
- # Side loads the class, and its super classes that respond to `.path`. Assign the `abstract_class`
13
- # class variable to any abstract class, and it will not be side loaded.
14
15
  module Sideload
15
16
  def before_render
16
- klass = self.class
17
- while !klass.abstract_class && klass.respond_to?(:path) && klass.path
18
- Proscenium::SideLoad.append klass.path
19
- klass = klass.superclass
20
- end
17
+ Proscenium::SideLoad.sideload_inheritance_chain self
21
18
 
22
19
  super
23
20
  end
24
21
  end
25
22
 
26
23
  class << self
27
- attr_accessor :path, :abstract_class
24
+ attr_accessor :abstract_class
28
25
 
29
26
  def inherited(child)
30
- child.path = if caller_locations(1, 1).first.label == 'inherited'
31
- Pathname.new caller_locations(2, 1).first.path
32
- else
33
- Pathname.new caller_locations(1, 1).first.path
34
- end
35
-
36
- child.prepend Sideload if Rails.application.config.proscenium.side_load
27
+ child.prepend Sideload
37
28
 
38
29
  super
39
30
  end
40
31
  end
41
-
42
- # @override Auto compilation of class names to css modules.
43
- def render_in(...)
44
- cssm.compile_class_names(super(...))
45
- end
46
-
47
- private
48
-
49
- # Overrides ActionView::Helpers::TagHelper::TagBuilder, allowing us to intercept the
50
- # `css_module` option from the HTML options argument of the `tag` and `content_tag` helpers, and
51
- # prepend it to the HTML `class` attribute.
52
- def tag_builder
53
- @tag_builder ||= Proscenium::ViewComponent::TagBuilder.new(self)
54
- end
55
32
  end
data/lib/proscenium.rb CHANGED
@@ -5,18 +5,36 @@ require 'active_support/dependencies/autoload'
5
5
  module Proscenium
6
6
  extend ActiveSupport::Autoload
7
7
 
8
- autoload :Current
8
+ FILE_EXTENSIONS = ['js', 'mjs', 'ts', 'jsx', 'tsx', 'css', 'js.map', 'mjs.map', 'jsx.map',
9
+ 'ts.map', 'tsx.map', 'css.map'].freeze
10
+
11
+ APPLICATION_INCLUDE_PATHS = ['config', 'app/assets', 'app/views', 'app/components', 'lib',
12
+ 'node_modules'].freeze
13
+
14
+ # Environment variables that should always be passed to the builder.
15
+ DEFAULT_ENV_VARS = Set['RAILS_ENV', 'NODE_ENV'].freeze
16
+
17
+ autoload :SourcePath
18
+ autoload :Utils
19
+ autoload :Monkey
9
20
  autoload :Middleware
21
+ autoload :EnsureLoaded
10
22
  autoload :SideLoad
11
23
  autoload :CssModule
24
+ autoload :ReactComponentable
12
25
  autoload :ViewComponent
13
26
  autoload :Phlex
14
27
  autoload :Helper
15
- autoload :Esbuild
16
-
17
- def self.reset_current_side_loaded
18
- Current.reset
19
- Current.loaded = SideLoad::EXTENSIONS.to_h { |e| [e, Set.new] }
28
+ autoload :Builder
29
+ autoload :Importer
30
+ autoload :Resolver
31
+
32
+ class Deprecator
33
+ def deprecation_warning(name, message, _caller_backtrace = nil)
34
+ msg = "`#{name}` is deprecated and will be removed in a near future release of Proscenium"
35
+ msg << " (#{message})" if message
36
+ Kernel.warn msg
37
+ end
20
38
  end
21
39
 
22
40
  class PathResolutionFailed < StandardError
@@ -29,68 +47,6 @@ module Proscenium
29
47
  "Path #{@path.inspect} cannot be resolved"
30
48
  end
31
49
  end
32
-
33
- module Utils
34
- module_function
35
-
36
- # @param value [#to_s] The value to create the digest from. This will usually be a `Pathname`.
37
- # @return [String] string digest of the given value.
38
- def digest(value)
39
- Digest::SHA1.hexdigest(value.to_s)[..7]
40
- end
41
-
42
- # Resolve the given `path` to a URL path.
43
- #
44
- # @param path [String] Can be URL path, file system path, or bare specifier (ie. NPM package).
45
- # @return [String] URL path.
46
- def resolve_path(path) # rubocop:disable Metrics/AbcSize
47
- raise ArgumentError, 'path must be a string' unless path.is_a?(String)
48
-
49
- if path.starts_with?('./', '../')
50
- raise ArgumentError, 'path must be an absolute file system or URL path'
51
- end
52
-
53
- matched_gem = Proscenium.config.side_load_gems.find do |_, opts|
54
- path.starts_with?("#{opts[:root]}/")
55
- end
56
-
57
- if matched_gem
58
- sroot = "#{matched_gem[1][:root]}/"
59
- relpath = path.delete_prefix(sroot)
60
-
61
- if (package_name = matched_gem[1][:package_name] || matched_gem[0])
62
- return Esbuild::Golib.resolve("#{package_name}/#{relpath}")
63
- end
64
-
65
- # TODO: manually resolve the path without esbuild
66
- raise PathResolutionFailed, path
67
- end
68
-
69
- return path.delete_prefix(Rails.root.to_s) if path.starts_with?("#{Rails.root}/")
70
-
71
- Esbuild::Golib.resolve(path)
72
- end
73
-
74
- # Resolves CSS class `names` to CSS module names. Each name will be converted to a CSS module
75
- # name, consisting of the camelCased name (lower case first character), and suffixed with the
76
- # given `digest`.
77
- #
78
- # @param names [String, Array]
79
- # @param digest: [String]
80
- # @returns [Array] of class names generated from the given CSS module `names` and `digest`.
81
- def css_modularise_class_names(*names, digest: nil)
82
- names.flatten.compact.map { |name| css_modularise_class_name name, digest: digest }
83
- end
84
-
85
- def css_modularise_class_name(name, digest: nil)
86
- sname = name.to_s
87
- if sname.starts_with?('_')
88
- "_#{sname[1..].camelize(:lower)}#{digest}"
89
- else
90
- "#{sname.camelize(:lower)}#{digest}"
91
- end
92
- end
93
- end
94
50
  end
95
51
 
96
52
  require 'proscenium/railtie'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: proscenium
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.1
4
+ version: 0.11.0.pre.1
5
5
  platform: x86_64-linux
6
6
  authors:
7
7
  - Joel Moss
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-06-11 00:00:00.000000000 Z
11
+ date: 2023-09-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -44,20 +44,6 @@ dependencies:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
46
  version: 1.15.5
47
- - !ruby/object:Gem::Dependency
48
- name: nokogiri
49
- requirement: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - "~>"
52
- - !ruby/object:Gem::Version
53
- version: '1.13'
54
- type: :runtime
55
- prerelease: false
56
- version_requirements: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - "~>"
59
- - !ruby/object:Gem::Version
60
- version: '1.13'
61
47
  - !ruby/object:Gem::Dependency
62
48
  name: oj
63
49
  requirement: !ruby/object:Gem::Requirement
@@ -72,34 +58,6 @@ dependencies:
72
58
  - - "~>"
73
59
  - !ruby/object:Gem::Version
74
60
  version: '3.13'
75
- - !ruby/object:Gem::Dependency
76
- name: phlex
77
- requirement: !ruby/object:Gem::Requirement
78
- requirements:
79
- - - "~>"
80
- - !ruby/object:Gem::Version
81
- version: 1.8.1
82
- type: :runtime
83
- prerelease: false
84
- version_requirements: !ruby/object:Gem::Requirement
85
- requirements:
86
- - - "~>"
87
- - !ruby/object:Gem::Version
88
- version: 1.8.1
89
- - !ruby/object:Gem::Dependency
90
- name: phlex-rails
91
- requirement: !ruby/object:Gem::Requirement
92
- requirements:
93
- - - "~>"
94
- - !ruby/object:Gem::Version
95
- version: 1.0.0
96
- type: :runtime
97
- prerelease: false
98
- version_requirements: !ruby/object:Gem::Requirement
99
- requirements:
100
- - - "~>"
101
- - !ruby/object:Gem::Version
102
- version: 1.0.0
103
61
  - !ruby/object:Gem::Dependency
104
62
  name: railties
105
63
  requirement: !ruby/object:Gem::Requirement
@@ -131,34 +89,39 @@ files:
131
89
  - LICENSE.txt
132
90
  - README.md
133
91
  - lib/proscenium.rb
92
+ - lib/proscenium/builder.rb
134
93
  - lib/proscenium/css_module.rb
135
- - lib/proscenium/css_module/class_names_resolver.rb
136
- - lib/proscenium/css_module/resolver.rb
137
- - lib/proscenium/current.rb
138
- - lib/proscenium/esbuild.rb
139
- - lib/proscenium/esbuild/golib.rb
94
+ - lib/proscenium/css_module/path.rb
95
+ - lib/proscenium/css_module/transformer.rb
96
+ - lib/proscenium/ensure_loaded.rb
140
97
  - lib/proscenium/ext/proscenium
141
98
  - lib/proscenium/ext/proscenium.h
142
99
  - lib/proscenium/helper.rb
100
+ - lib/proscenium/importer.rb
101
+ - lib/proscenium/libs/react-manager/index.jsx
102
+ - lib/proscenium/libs/react-manager/react.js
103
+ - lib/proscenium/libs/stimulus-loading.js
143
104
  - lib/proscenium/log_subscriber.rb
144
105
  - lib/proscenium/middleware.rb
145
106
  - lib/proscenium/middleware/base.rb
146
107
  - lib/proscenium/middleware/esbuild.rb
147
108
  - lib/proscenium/middleware/url.rb
109
+ - lib/proscenium/monkey.rb
148
110
  - lib/proscenium/phlex.rb
149
- - lib/proscenium/phlex/component_concerns.rb
111
+ - lib/proscenium/phlex/css_modules.rb
150
112
  - lib/proscenium/phlex/page.rb
151
113
  - lib/proscenium/phlex/react_component.rb
152
- - lib/proscenium/phlex/resolve_css_modules.rb
153
114
  - lib/proscenium/railtie.rb
115
+ - lib/proscenium/react_componentable.rb
116
+ - lib/proscenium/resolver.rb
154
117
  - lib/proscenium/side_load.rb
155
- - lib/proscenium/side_load/ensure_loaded.rb
156
- - lib/proscenium/side_load/helper.rb
157
- - lib/proscenium/side_load/monkey.rb
118
+ - lib/proscenium/source_path.rb
119
+ - lib/proscenium/utils.rb
158
120
  - lib/proscenium/version.rb
159
121
  - lib/proscenium/view_component.rb
122
+ - lib/proscenium/view_component/css_modules.rb
160
123
  - lib/proscenium/view_component/react_component.rb
161
- - lib/proscenium/view_component/tag_builder.rb
124
+ - lib/proscenium/view_component/sideload.rb
162
125
  homepage: https://github.com/joelmoss/proscenium
163
126
  licenses:
164
127
  - MIT
@@ -178,11 +141,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
178
141
  version: 2.7.0
179
142
  required_rubygems_version: !ruby/object:Gem::Requirement
180
143
  requirements:
181
- - - ">="
144
+ - - ">"
182
145
  - !ruby/object:Gem::Version
183
- version: '0'
146
+ version: 1.3.1
184
147
  requirements: []
185
- rubygems_version: 3.4.13
148
+ rubygems_version: 3.4.17
186
149
  signing_key:
187
150
  specification_version: 4
188
151
  summary: The engine powering your Rails frontend
@@ -1,66 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Proscenium
4
- class CssModule::ClassNamesResolver
5
- def initialize(class_names, phlex_path)
6
- @class_names = class_names.split
7
- @stylesheets = {}
8
- @phlex_path = phlex_path.sub_ext('.module.css')
9
-
10
- resolve_class_names
11
- end
12
-
13
- def class_names
14
- @class_names.join(' ')
15
- end
16
-
17
- def stylesheets
18
- @stylesheets.map { |_, values| values[:resolved_path] }
19
- end
20
-
21
- private
22
-
23
- def resolve_class_names
24
- @class_names.map! do |class_name|
25
- if class_name.include?('/')
26
- if class_name.starts_with?('@')
27
- # Scoped bare specifier (eg. "@scoped/package/lib/button@default").
28
- _, path, name = class_name.split('@')
29
- path = "@#{path}"
30
- elsif class_name.starts_with?('/')
31
- # Local path with leading slash.
32
- path, name = class_name[1..].split('@')
33
- else
34
- # Bare specifier (eg. "mypackage/lib/button@default").
35
- path, name = class_name.split('@')
36
- end
37
-
38
- path += '.module.css'
39
-
40
- Utils.css_modularise_class_name name, digest: add_stylesheet(path)[:digest]
41
- elsif class_name.starts_with?('@')
42
- Utils.css_modularise_class_name class_name[1..],
43
- digest: add_stylesheet(@phlex_path)[:digest]
44
- else
45
- class_name
46
- end
47
- end
48
- end
49
-
50
- def add_stylesheet(path)
51
- return @stylesheets[path] if @stylesheets.key?(path)
52
-
53
- resolved_path = Utils.resolve_path(path.to_s)
54
-
55
- unless Rails.root.join(resolved_path[1..]).exist?
56
- raise CssModule::StylesheetNotFound, resolved_path
57
- end
58
-
59
- # Note that the digest is based on the resolved (URL) path, not the original path.
60
- @stylesheets[path] = {
61
- resolved_path: resolved_path,
62
- digest: Utils.digest(resolved_path)
63
- }
64
- end
65
- end
66
- end
@@ -1,76 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Proscenium
4
- class CssModule::Resolver
5
- attr_reader :side_loaded_paths
6
-
7
- # @param path [Pathname] Absolute file system path to the Ruby file that will be side loaded.
8
- def initialize(path, side_load: true, hash: nil)
9
- raise ArgumentError, "'#{path}' must be a `Pathname`" unless path.is_a?(Pathname)
10
-
11
- @path = path
12
- @hash = hash
13
- @css_module_path = path.sub_ext('.module.css')
14
- @side_load = side_load
15
- @side_loaded_paths = nil
16
- end
17
-
18
- # Parses the given `content` for CSS modules names ('class' attributes beginning with '@'), and
19
- # returns the content with said CSS Modules replaced with the compiled class names.
20
- #
21
- # Example:
22
- # <div class="@my_css_module_name"></div>
23
- def compile_class_names(content)
24
- doc = Nokogiri::HTML::DocumentFragment.parse(content)
25
-
26
- return content if (modules = doc.css('[class*="@"]')).empty?
27
-
28
- modules.each do |ele|
29
- classes = ele.classes.map { |cls| cls.starts_with?('@') ? class_names!(cls[1..]) : cls }
30
- ele['class'] = classes.join(' ')
31
- end
32
-
33
- doc.to_html.html_safe
34
- end
35
-
36
- # Resolves the given CSS class names to CSS modules. This will also side load the stylesheet if
37
- # it exists.
38
- #
39
- # @param names [String, Array]
40
- # @returns [Array] of class names generated from the given CSS module `names`.
41
- def class_names(*names)
42
- side_load_css_module
43
- Utils.css_modularise_class_names names, digest: @hash
44
- end
45
-
46
- # Like #class_names, but requires that the stylesheet exists.
47
- #
48
- # @param names [String, Array]
49
- # @raises Proscenium::CssModule::NotFound if stylesheet does not exists.
50
- # @see #class_names
51
- def class_names!(...)
52
- raise CssModule::StylesheetNotFound, @css_module_path unless @css_module_path.exist?
53
-
54
- class_names(...)
55
- end
56
-
57
- def side_loaded?
58
- @side_loaded_paths.present?
59
- end
60
-
61
- private
62
-
63
- def side_load_css_module
64
- return if !@side_load || !Rails.application.config.proscenium.side_load
65
-
66
- paths = SideLoad.append @path, { '.module.css' => :css }
67
-
68
- @side_loaded_paths = if paths.empty?
69
- nil
70
- else
71
- @hash = Utils.digest(paths[0])
72
- paths
73
- end
74
- end
75
- end
76
- end
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'active_support/current_attributes'
4
-
5
- module Proscenium
6
- class Current < ActiveSupport::CurrentAttributes
7
- attribute :loaded
8
- end
9
- end