proscenium 0.9.1-arm64-darwin → 0.11.0-arm64-darwin

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +451 -65
  3. data/lib/proscenium/builder.rb +144 -0
  4. data/lib/proscenium/css_module/path.rb +31 -0
  5. data/lib/proscenium/css_module/transformer.rb +82 -0
  6. data/lib/proscenium/css_module.rb +12 -25
  7. data/lib/proscenium/ensure_loaded.rb +27 -0
  8. data/lib/proscenium/ext/proscenium +0 -0
  9. data/lib/proscenium/ext/proscenium.h +20 -12
  10. data/lib/proscenium/helper.rb +85 -0
  11. data/lib/proscenium/importer.rb +110 -0
  12. data/lib/proscenium/libs/react-manager/index.jsx +101 -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/libs/test.js +1 -0
  16. data/lib/proscenium/log_subscriber.rb +1 -2
  17. data/lib/proscenium/middleware/base.rb +8 -8
  18. data/lib/proscenium/middleware/engines.rb +37 -0
  19. data/lib/proscenium/middleware/esbuild.rb +3 -5
  20. data/lib/proscenium/middleware/runtime.rb +18 -0
  21. data/lib/proscenium/middleware.rb +19 -4
  22. data/lib/proscenium/{side_load/monkey.rb → monkey.rb} +24 -12
  23. data/lib/proscenium/phlex/{resolve_css_modules.rb → css_modules.rb} +28 -16
  24. data/lib/proscenium/phlex/react_component.rb +27 -64
  25. data/lib/proscenium/phlex.rb +11 -30
  26. data/lib/proscenium/railtie.rb +49 -41
  27. data/lib/proscenium/react_componentable.rb +95 -0
  28. data/lib/proscenium/resolver.rb +37 -0
  29. data/lib/proscenium/side_load.rb +13 -72
  30. data/lib/proscenium/source_path.rb +15 -0
  31. data/lib/proscenium/templates/rescues/build_error.html.erb +30 -0
  32. data/lib/proscenium/utils.rb +13 -0
  33. data/lib/proscenium/version.rb +1 -1
  34. data/lib/proscenium/view_component/css_modules.rb +11 -0
  35. data/lib/proscenium/view_component/react_component.rb +15 -28
  36. data/lib/proscenium/view_component/sideload.rb +4 -0
  37. data/lib/proscenium/view_component.rb +8 -31
  38. data/lib/proscenium.rb +23 -68
  39. metadata +25 -59
  40. data/lib/proscenium/css_module/class_names_resolver.rb +0 -66
  41. data/lib/proscenium/css_module/resolver.rb +0 -76
  42. data/lib/proscenium/current.rb +0 -9
  43. data/lib/proscenium/esbuild/golib.rb +0 -97
  44. data/lib/proscenium/esbuild.rb +0 -32
  45. data/lib/proscenium/phlex/component_concerns.rb +0 -27
  46. data/lib/proscenium/phlex/page.rb +0 -62
  47. data/lib/proscenium/side_load/ensure_loaded.rb +0 -25
  48. data/lib/proscenium/side_load/helper.rb +0 -25
  49. data/lib/proscenium/view_component/tag_builder.rb +0 -23
@@ -6,11 +6,6 @@ require 'proscenium/log_subscriber'
6
6
  ENV['RAILS_ENV'] = Rails.env
7
7
 
8
8
  module Proscenium
9
- FILE_EXTENSIONS = ['js', 'mjs', 'ts', 'jsx', 'tsx', 'css', 'js.map', 'mjs.map', 'jsx.map',
10
- 'ts.map', 'tsx.map', 'css.map'].freeze
11
-
12
- APPLICATION_INCLUDE_PATHS = ['config', 'app/assets', 'app/views', 'lib', 'node_modules'].freeze
13
-
14
9
  class << self
15
10
  def config
16
11
  @config ||= Railtie.config.proscenium
@@ -21,61 +16,74 @@ module Proscenium
21
16
  isolate_namespace Proscenium
22
17
 
23
18
  config.proscenium = ActiveSupport::OrderedOptions.new
19
+ config.proscenium.debug = false
24
20
  config.proscenium.side_load = true
21
+ config.proscenium.code_splitting = true
22
+
23
+ # TODO: implement!
25
24
  config.proscenium.cache_query_string = Rails.env.production? && ENV.fetch('REVISION', nil)
26
25
  config.proscenium.cache_max_age = 2_592_000 # 30 days
27
- config.proscenium.include_paths = Set.new(APPLICATION_INCLUDE_PATHS)
28
26
 
29
- # A hash of gems that can be side loaded. Assets from gems listed here can be side loaded.
30
- #
31
- # Because side loading uses URL paths, any gem dependencies that side load assets will fail,
32
- # because the URL path will be relative to the application's root, and not the gem's root. By
33
- # specifying a list of gems that can be side loaded, Proscenium will be able to resolve the URL
34
- # path to the gem's root, and side load the asset.
27
+ # List of environment variable names that should be passed to the builder, which will then be
28
+ # passed to esbuild's `Define` option. Being explicit about which environment variables are
29
+ # defined means a faster build, as esbuild will have less to do.
30
+ config.proscenium.env_vars = Set.new
31
+
32
+ # Rails engines to expose and allow Proscenium to serve their assets.
35
33
  #
36
- # Side loading gems rely on NPM and a package.json file in the gem root. This ensures that any
37
- # dependencies are resolved correctly. This is required even if your gem has no package
38
- # dependencies.
34
+ # A Rails engine that has assets, can add Proscenium as a gem dependency, and then add itself
35
+ # to this list. Proscenium will then serve the engine's assets at the URL path beginning with
36
+ # the engine name.
39
37
  #
40
38
  # Example:
41
- # config.proscenium.side_load_gems['mygem'] = {
42
- # root: gem_root,
43
- # package_name: 'mygem'
44
- # }
45
- config.proscenium.side_load_gems = {}
39
+ # class Gem1::Engine < ::Rails::Engine
40
+ # config.proscenium.engines << self
41
+ # end
42
+ config.proscenium.engines = Set.new
46
43
 
47
- initializer 'proscenium.configuration' do |app|
48
- options = app.config.proscenium
49
- options.include_paths = Set.new(APPLICATION_INCLUDE_PATHS) if options.include_paths.blank?
44
+ config.action_dispatch.rescue_templates = {
45
+ 'Proscenium::Builder::BuildError' => 'build_error'
46
+ }
47
+
48
+ initializer 'proscenium.debugging' do
49
+ if Rails.gem_version >= Gem::Version.new('7.1.0')
50
+ tpl_path = root.join('lib', 'proscenium', 'templates').to_s
51
+ ActionDispatch::DebugView::RESCUES_TEMPLATE_PATHS << tpl_path
52
+ end
50
53
  end
51
54
 
52
55
  initializer 'proscenium.middleware' do |app|
53
- app.middleware.insert_after ActionDispatch::Static, Proscenium::Middleware
54
- app.middleware.insert_after ActionDispatch::Static, Rack::ETag, 'no-cache'
55
- app.middleware.insert_after ActionDispatch::Static, Rack::ConditionalGet
56
+ app.middleware.insert_after ActionDispatch::Static, Middleware
57
+ # app.middleware.insert_after ActionDispatch::Static, Rack::ETag, 'no-cache'
58
+ # app.middleware.insert_after ActionDispatch::Static, Rack::ConditionalGet
56
59
  end
57
60
 
58
- initializer 'proscenium.side_loading' do |app|
59
- if app.config.proscenium.side_load
60
- Proscenium::Current.loaded ||= SideLoad::EXTENSIONS.to_h { |e| [e, Set.new] }
61
-
62
- ActiveSupport.on_load(:action_view) do
63
- ActionView::Base.include Proscenium::SideLoad::Helper
64
-
65
- ActionView::TemplateRenderer.prepend SideLoad::Monkey::TemplateRenderer
66
- ActionView::PartialRenderer.prepend SideLoad::Monkey::PartialRenderer
67
- end
68
-
69
- ActiveSupport.on_load(:action_controller) do
70
- ActionController::Base.include Proscenium::SideLoad::EnsureLoaded
71
- end
61
+ initializer 'proscenium.monkey_patches' do
62
+ ActiveSupport.on_load(:action_view) do
63
+ ActionView::TemplateRenderer.prepend Monkey::TemplateRenderer
64
+ ActionView::PartialRenderer.prepend Monkey::PartialRenderer
72
65
  end
73
66
  end
74
67
 
75
68
  initializer 'proscenium.helper' do
76
69
  ActiveSupport.on_load(:action_view) do
77
- ActionView::Base.include Proscenium::Helper
70
+ ActionView::Base.include Helper
71
+ end
72
+
73
+ ActiveSupport.on_load(:action_controller) do
74
+ ActionController::Base.include EnsureLoaded
78
75
  end
79
76
  end
80
77
  end
81
78
  end
79
+
80
+ if Rails.gem_version < Gem::Version.new('7.1.0')
81
+ class ActionDispatch::DebugView
82
+ def initialize(assigns)
83
+ tpl_path = Proscenium::Railtie.root.join('lib', 'proscenium', 'templates').to_s
84
+ paths = [RESCUES_TEMPLATE_PATH, tpl_path]
85
+ lookup_context = ActionView::LookupContext.new(paths)
86
+ super(lookup_context, assigns, nil)
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium
4
+ module ReactComponentable
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ # @return [Hash] the props to pass to the React component.
9
+ attr_writer :props
10
+
11
+ # The HTML tag to use as the wrapping element for the component. You can reassign this in your
12
+ # component class to use a different tag:
13
+ #
14
+ # class MyComponent < Proscenium::ViewComponent::ReactComponent
15
+ # self.root_tag = :span
16
+ # end
17
+ #
18
+ # @return [Symbol]
19
+ class_attribute :root_tag, instance_predicate: false, default: :div
20
+
21
+ # By default, the template block (content) of the component will be server rendered as normal.
22
+ # However, when React hydrates and takes control of the component, it's content will be
23
+ # replaced by React with the JavaScript rendered content. Enabling this option will forward
24
+ # the server rendered content as the `children` prop passed to the React component.
25
+ #
26
+ # @example
27
+ #
28
+ # const Component = ({ children }) => {
29
+ # return <div dangerouslySetInnerHTML={{ __html: children }} />
30
+ # }
31
+ #
32
+ # @return [Boolean]
33
+ class_attribute :forward_children, default: false
34
+
35
+ # Lazy load the component using IntersectionObserver?
36
+ #
37
+ # @return [Boolean]
38
+ class_attribute :lazy, default: false
39
+
40
+ class_attribute :loader
41
+
42
+ # @return [String] the URL path to the component manager.
43
+ class_attribute :manager, default: '/@proscenium/react-manager/index.jsx'
44
+ end
45
+
46
+ class_methods do
47
+ def sideload
48
+ Importer.import manager
49
+ Importer.sideload source_path, lazy: true
50
+ end
51
+ end
52
+
53
+ # @param props: [Hash]
54
+ def initialize(lazy: self.class.lazy, loader: self.class.loader, props: {})
55
+ self.lazy = lazy
56
+ self.loader = loader
57
+ @props = props
58
+ end
59
+
60
+ # The absolute URL path to the javascript component.
61
+ def virtual_path
62
+ @virtual_path ||= Resolver.resolve self.class.source_path.sub_ext('.jsx').to_s
63
+ end
64
+
65
+ def props
66
+ @props ||= {}
67
+ end
68
+
69
+ private
70
+
71
+ def data_attributes
72
+ {
73
+ proscenium_component_path: Pathname.new(virtual_path).to_s,
74
+ proscenium_component_props: prepared_props,
75
+ proscenium_component_lazy: lazy
76
+ }.tap do |x|
77
+ x[:proscenium_component_forward_children] = true if forward_children?
78
+ end
79
+ end
80
+
81
+ def prepared_props
82
+ props.deep_transform_keys do |term|
83
+ # This ensures that the first letter after a slash is not capitalized.
84
+ string = term.to_s.split('/').map { |str| str.camelize :lower }.join('/')
85
+
86
+ # Reverses the effect of ActiveSupport::Inflector.camelize converting slashes into `::`.
87
+ string.gsub '::', '/'
88
+ end.to_json
89
+ end
90
+
91
+ def loader_component
92
+ render Loader::Component.new(loader, @html_class, data_attributes, tag: @html_tag)
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/current_attributes'
4
+
5
+ module Proscenium
6
+ class Resolver < ActiveSupport::CurrentAttributes
7
+ # TODO: cache this across requests in production.
8
+ attribute :resolved
9
+
10
+ # Resolve the given `path` to a URL path.
11
+ #
12
+ # @param path [String] Can be URL path, file system path, or bare specifier (ie. NPM package).
13
+ # @return [String] URL path.
14
+ #
15
+ # rubocop:disable Metrics/*
16
+ def self.resolve(path)
17
+ self.resolved ||= {}
18
+
19
+ self.resolved[path] ||= begin
20
+ if path.start_with?('./', '../')
21
+ raise ArgumentError, 'path must be an absolute file system or URL path'
22
+ end
23
+
24
+ if path.start_with?('@proscenium/')
25
+ "/#{path}"
26
+ elsif (engine = Proscenium.config.engines.find { |e| path.start_with? "#{e.root}/" })
27
+ path.sub(/^#{engine.root}/, "/#{engine.engine_name}")
28
+ elsif path.start_with?("#{Rails.root}/")
29
+ path.delete_prefix Rails.root.to_s
30
+ else
31
+ Builder.resolve path
32
+ end
33
+ end
34
+ end
35
+ # rubocop:enable Metrics/*
36
+ end
37
+ end
@@ -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,30 @@
1
+ <header>
2
+ <h1>
3
+ <%= Rails.gem_version >= Gem::Version.new('7.1.0') ? @exception_wrapper.exception_class_name : @exception.class.to_s %>
4
+ <% if params_valid? && @request.parameters['controller'] %>
5
+ in <%= @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%= @request.parameters['action'] %><% end %>
6
+ <% end %>
7
+ </h1>
8
+ </header>
9
+
10
+ <main role="main" id="container">
11
+ <%= render "rescues/message_and_suggestions", exception: @exception, exception_wrapper: Rails.gem_version >= Gem::Version.new('7.1.0') ? @exception_wrapper : nil %>
12
+
13
+ <% if @exception.error['location'] %>
14
+ <div class="source">
15
+ <div class="data">
16
+ <pre>
17
+
18
+ <%= @exception.error['location']['file'] %>:<%= @exception.error['location']['line'] %>:<%= @exception.error['location']['column'] %>
19
+
20
+ <%= @exception.error['location']['line'].to_s.rjust 5 %> │ <%= @exception.error['location']['line_text'] %>
21
+ │ <%= (@exception.error['location']['length'] > 1 ? "~" * @exception.error['location']['length'] : "^").rjust(@exception.error['location']['column'] + @exception.error['location']['length']) %>
22
+ <%- if @exception.error['location']['suggestion'].present? -%> + │ <%= @exception.error['location']['suggestion'].rjust(@exception.error['location']['column'] + 1) %>
23
+ <% else %> <%- end -%>
24
+ </pre>
25
+ </div>
26
+ </div>
27
+ <% end %>
28
+
29
+ <%= render template: "rescues/_request_and_response" %>
30
+ </main>
@@ -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'
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,35 @@ 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
+ ALLOWED_DIRECTORIES = 'app,lib,config,vendor,node_modules'
12
+
13
+ # Environment variables that should always be passed to the builder.
14
+ DEFAULT_ENV_VARS = Set['RAILS_ENV', 'NODE_ENV'].freeze
15
+
16
+ autoload :SourcePath
17
+ autoload :Utils
18
+ autoload :Monkey
9
19
  autoload :Middleware
20
+ autoload :EnsureLoaded
10
21
  autoload :SideLoad
11
22
  autoload :CssModule
23
+ autoload :ReactComponentable
12
24
  autoload :ViewComponent
13
25
  autoload :Phlex
14
26
  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] }
27
+ autoload :Builder
28
+ autoload :Importer
29
+ autoload :Resolver
30
+
31
+ class Deprecator
32
+ def deprecation_warning(name, message, _caller_backtrace = nil)
33
+ msg = "`#{name}` is deprecated and will be removed in a near future release of Proscenium"
34
+ msg << " (#{message})" if message
35
+ Kernel.warn msg
36
+ end
20
37
  end
21
38
 
22
39
  class PathResolutionFailed < StandardError
@@ -29,68 +46,6 @@ module Proscenium
29
46
  "Path #{@path.inspect} cannot be resolved"
30
47
  end
31
48
  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
49
  end
95
50
 
96
51
  require 'proscenium/railtie'