proscenium 0.9.1-aarch64-linux → 0.11.0-aarch64-linux

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 (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'