proscenium 0.10.0-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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +207 -45
  3. data/lib/proscenium/builder.rb +41 -19
  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 +3 -2
  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/test.js +1 -0
  15. data/lib/proscenium/middleware/base.rb +7 -7
  16. data/lib/proscenium/middleware/engines.rb +37 -0
  17. data/lib/proscenium/middleware/esbuild.rb +3 -5
  18. data/lib/proscenium/middleware/runtime.rb +18 -0
  19. data/lib/proscenium/middleware.rb +14 -4
  20. data/lib/proscenium/{side_load/monkey.rb → monkey.rb} +19 -15
  21. data/lib/proscenium/phlex/{resolve_css_modules.rb → css_modules.rb} +28 -16
  22. data/lib/proscenium/phlex/react_component.rb +26 -27
  23. data/lib/proscenium/phlex.rb +11 -30
  24. data/lib/proscenium/railtie.rb +44 -46
  25. data/lib/proscenium/react_componentable.rb +95 -0
  26. data/lib/proscenium/resolver.rb +37 -0
  27. data/lib/proscenium/side_load.rb +13 -73
  28. data/lib/proscenium/source_path.rb +15 -0
  29. data/lib/proscenium/templates/rescues/build_error.html.erb +30 -0
  30. data/lib/proscenium/utils.rb +13 -0
  31. data/lib/proscenium/version.rb +1 -1
  32. data/lib/proscenium/view_component/css_modules.rb +11 -0
  33. data/lib/proscenium/view_component/react_component.rb +15 -15
  34. data/lib/proscenium/view_component/sideload.rb +4 -0
  35. data/lib/proscenium/view_component.rb +8 -38
  36. data/lib/proscenium.rb +22 -68
  37. metadata +23 -30
  38. data/lib/proscenium/componentable.rb +0 -63
  39. data/lib/proscenium/css_module/class_names_resolver.rb +0 -66
  40. data/lib/proscenium/css_module/resolver.rb +0 -76
  41. data/lib/proscenium/current.rb +0 -9
  42. data/lib/proscenium/phlex/component_concerns.rb +0 -9
  43. data/lib/proscenium/phlex/page.rb +0 -62
  44. data/lib/proscenium/side_load/ensure_loaded.rb +0 -25
  45. data/lib/proscenium/side_load/helper.rb +0 -41
  46. data/lib/proscenium/view_component/tag_builder.rb +0 -23
@@ -1,15 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Proscenium
4
- module Phlex::ResolveCssModules
5
- extend ActiveSupport::Concern
4
+ module Phlex::CssModules
5
+ include Proscenium::CssModule
6
6
 
7
- class_methods do
8
- attr_accessor :side_load_cache
7
+ def self.included(base)
8
+ base.extend CssModule::Path
9
+ base.extend ClassMethods
10
+ end
11
+
12
+ module ClassMethods
13
+ # Set of CSS module paths that have been resolved after being transformed from 'class' HTML
14
+ # attributes. See #process_attributes. This is here because Phlex caches attributes. Which
15
+ # means while the CSS class names will be transformed, any resolved paths will be lost in
16
+ # subsequent requests.
17
+ attr_accessor :resolved_css_module_paths
9
18
  end
10
19
 
11
20
  def before_template
12
- self.class.side_load_cache ||= Set.new
21
+ self.class.resolved_css_module_paths ||= Concurrent::Set.new
22
+ super
23
+ end
24
+
25
+ def after_template
26
+ self.class.resolved_css_module_paths.each do |path|
27
+ Proscenium::Importer.import path
28
+ end
29
+
13
30
  super
14
31
  end
15
32
 
@@ -44,24 +61,19 @@ module Proscenium
44
61
  # end
45
62
  # end
46
63
  #
47
- # The given class name should be underscored, and the resulting CSS module name will be
48
- # `camelCased` with a lower case first character.
49
- #
50
64
  # @raise [Proscenium::CssModule::Resolver::NotFound] If a CSS module file is not found for the
51
65
  # Phlex class file path.
52
66
  def process_attributes(**attributes)
53
67
  if attributes.key?(:class) && (attributes[:class] = tokens(attributes[:class])).include?('@')
54
- resolver = CssModule::ClassNamesResolver.new(attributes[:class], path)
55
- self.class.side_load_cache.merge resolver.stylesheets
56
- attributes[:class] = resolver.class_names
68
+ names = attributes[:class].is_a?(Array) ? attributes[:class] : attributes[:class].split
69
+
70
+ attributes[:class] = cssm.class_names(*names).map do |name, path|
71
+ self.class.resolved_css_module_paths << path if path
72
+ name
73
+ end
57
74
  end
58
75
 
59
76
  attributes
60
77
  end
61
-
62
- def after_template
63
- super
64
- self.class.side_load_cache&.each { |path| SideLoad.append! path, :css }
65
- end
66
78
  end
67
79
  end
@@ -1,33 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- #
4
- # Renders a <div> for use with React components, with data attributes specifying the component path
5
- # and props.
6
- #
7
- # If a block is given, it will be yielded within the div, allowing for a custom "loading" UI. If no
8
- # block is given, then a "loading..." text will be rendered. It is intended that the component is
9
- # mounted to this div, and the loading UI will then be replaced with the component's rendered
10
- # output.
11
- #
12
- # You can pass props to the component in the `:props` keyword argument.
13
- #
14
- class Proscenium::Phlex::ReactComponent < Proscenium::Phlex
15
- self.abstract_class = true
16
-
17
- include Proscenium::Componentable
18
- include Proscenium::Phlex::ComponentConcerns::CssModules
19
-
20
- # Override this to provide your own loading UI.
3
+ module Proscenium
4
+ # Renders a <div> for use with React components, with data attributes specifying the component
5
+ # path and props.
21
6
  #
22
- # @example
23
- # def template(**attributes, &block)
24
- # super do
25
- # 'Look at me! I am loading now...'
26
- # end
27
- # end
7
+ # If a block is given, it will be yielded within the div, allowing for a custom "loading" UI. If
8
+ # no block is given, then a "loading..." text will be rendered. It is intended that the component
9
+ # is mounted to this div, and the loading UI will then be replaced with the component's rendered
10
+ # output.
28
11
  #
29
- # @yield the given block to a `div` within the top level component div.
30
- def template(**attributes, &block)
31
- send root_tag, **{ data: data_attributes }.deep_merge(attributes), &block
12
+ # You can pass props to the component in the `:props` keyword argument.
13
+ class Phlex::ReactComponent < Phlex
14
+ self.abstract_class = true
15
+
16
+ include ReactComponentable
17
+
18
+ # Override this to provide your own loading UI.
19
+ #
20
+ # @example
21
+ # def template(**attributes, &block)
22
+ # super do
23
+ # 'Look at me! I am loading now...'
24
+ # end
25
+ # end
26
+ #
27
+ # @yield the given block to a `div` within the top level component div.
28
+ def template(**attributes, &block)
29
+ send root_tag, **{ data: data_attributes }.deep_merge(attributes), &block
30
+ end
32
31
  end
33
32
  end
@@ -5,54 +5,35 @@ require 'phlex-rails'
5
5
  module Proscenium
6
6
  class Phlex < ::Phlex::HTML
7
7
  extend ActiveSupport::Autoload
8
- include Proscenium::CssModule
9
8
 
10
- autoload :Page
9
+ autoload :CssModules
11
10
  autoload :ReactComponent
12
- autoload :ResolveCssModules
13
- autoload :ComponentConcerns
14
11
 
15
12
  extend ::Phlex::Rails::HelperMacros
16
13
  include ::Phlex::Rails::Helpers::JavaScriptIncludeTag
17
14
  include ::Phlex::Rails::Helpers::StyleSheetLinkTag
15
+ include Proscenium::SourcePath
16
+ include CssModules
18
17
 
19
- define_output_helper :side_load_stylesheets
20
- define_output_helper :side_load_javascripts
18
+ define_output_helper :side_load_stylesheets # deprecated
19
+ define_output_helper :include_stylesheets
20
+ define_output_helper :side_load_javascripts # deprecated
21
+ define_output_helper :include_javascripts
22
+ define_output_helper :declare_lazy_scripts
21
23
 
22
- # Side loads the class, and its super classes that respond to `.path`. Assign the
23
- # `abstract_class` class variable to any abstract class, and it will not be side loaded.
24
- # Additionally, if the class responds to `side_load`, then that method is called.
25
24
  module Sideload
26
25
  def before_template
27
- klass = self.class
28
-
29
- if !klass.abstract_class && respond_to?(:side_load, true)
30
- side_load
31
- klass = klass.superclass
32
- end
33
-
34
- while !klass.abstract_class && klass.respond_to?(:path) && klass.path
35
- Proscenium::SideLoad.append klass.path
36
- klass = klass.superclass
37
- end
26
+ Proscenium::SideLoad.sideload_inheritance_chain self
38
27
 
39
28
  super
40
29
  end
41
30
  end
42
31
 
43
32
  class << self
44
- attr_accessor :path, :abstract_class
33
+ attr_accessor :abstract_class
45
34
 
46
35
  def inherited(child)
47
- unless child.path
48
- child.path = if caller_locations(1, 1).first.label == 'inherited'
49
- Pathname.new caller_locations(2, 1).first.path
50
- else
51
- Pathname.new caller_locations(1, 1).first.path
52
- end
53
- end
54
-
55
- child.prepend Sideload if Rails.application.config.proscenium.side_load
36
+ child.prepend Sideload
56
37
 
57
38
  super
58
39
  end
@@ -6,14 +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
- # Environment variables that should always be passed to the builder.
15
- DEFAULT_ENV_VARS = Set['RAILS_ENV', 'NODE_ENV'].freeze
16
-
17
9
  class << self
18
10
  def config
19
11
  @config ||= Railtie.config.proscenium
@@ -26,66 +18,72 @@ module Proscenium
26
18
  config.proscenium = ActiveSupport::OrderedOptions.new
27
19
  config.proscenium.debug = false
28
20
  config.proscenium.side_load = true
29
- config.proscenium.code_splitting = false
21
+ config.proscenium.code_splitting = true
22
+
23
+ # TODO: implement!
30
24
  config.proscenium.cache_query_string = Rails.env.production? && ENV.fetch('REVISION', nil)
31
25
  config.proscenium.cache_max_age = 2_592_000 # 30 days
32
- config.proscenium.include_paths = Set.new(APPLICATION_INCLUDE_PATHS)
33
26
 
34
27
  # List of environment variable names that should be passed to the builder, which will then be
35
28
  # passed to esbuild's `Define` option. Being explicit about which environment variables are
36
29
  # defined means a faster build, as esbuild will have less to do.
37
30
  config.proscenium.env_vars = Set.new
38
31
 
39
- # A hash of gems that can be side loaded. Assets from gems listed here can be side loaded.
40
- #
41
- # Because side loading uses URL paths, any gem dependencies that side load assets will fail,
42
- # because the URL path will be relative to the application's root, and not the gem's root. By
43
- # specifying a list of gems that can be side loaded, Proscenium will be able to resolve the URL
44
- # path to the gem's root, and side load the asset.
32
+ # Rails engines to expose and allow Proscenium to serve their assets.
45
33
  #
46
- # Side loading gems rely on NPM and a package.json file in the gem root. This ensures that any
47
- # dependencies are resolved correctly. This is required even if your gem has no package
48
- # 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.
49
37
  #
50
38
  # Example:
51
- # config.proscenium.side_load_gems['mygem'] = {
52
- # root: gem_root,
53
- # package_name: 'mygem'
54
- # }
55
- config.proscenium.side_load_gems = {}
56
-
57
- initializer 'proscenium.configuration' do |app|
58
- options = app.config.proscenium
59
- options.include_paths = Set.new(APPLICATION_INCLUDE_PATHS) if options.include_paths.blank?
39
+ # class Gem1::Engine < ::Rails::Engine
40
+ # config.proscenium.engines << self
41
+ # end
42
+ config.proscenium.engines = Set.new
43
+
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
60
53
  end
61
54
 
62
55
  initializer 'proscenium.middleware' do |app|
63
- app.middleware.insert_after ActionDispatch::Static, Proscenium::Middleware
64
- app.middleware.insert_after ActionDispatch::Static, Rack::ETag, 'no-cache'
65
- 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
66
59
  end
67
60
 
68
- initializer 'proscenium.side_loading' do |app|
69
- if app.config.proscenium.side_load
70
- Proscenium::Current.loaded ||= SideLoad::EXTENSIONS.to_h { |e| [e, Set.new] }
71
-
72
- ActiveSupport.on_load(:action_view) do
73
- ActionView::Base.include Proscenium::SideLoad::Helper
74
-
75
- ActionView::TemplateRenderer.prepend SideLoad::Monkey::TemplateRenderer
76
- ActionView::PartialRenderer.prepend SideLoad::Monkey::PartialRenderer
77
- end
78
-
79
- ActiveSupport.on_load(:action_controller) do
80
- ActionController::Base.include Proscenium::SideLoad::EnsureLoaded
81
- 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
82
65
  end
83
66
  end
84
67
 
85
68
  initializer 'proscenium.helper' do
86
69
  ActiveSupport.on_load(:action_view) do
87
- 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
88
75
  end
89
76
  end
90
77
  end
91
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,84 +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
- '.module.css' => :css,
16
- '.css' => :css,
17
- '.tsx' => :js,
18
- '.ts' => :js,
19
- '.jsx' => :js,
20
- '.js' => :js
21
- }.freeze
22
-
23
- attr_reader :path
24
-
25
5
  class << self
26
- # Side load the given asset `path`, by appending it to `Proscenium::Current.loaded`, which is
27
- # a Set of 'js' and 'css' asset paths. This is idempotent, so side loading will never include
28
- # duplicates.
6
+ # Side loads the class, and its super classes that respond to `.source_path`.
29
7
  #
30
- # @return [Array] appended URL paths
31
- def append(path, extension_map = EXTENSION_MAP)
32
- new(path, extension_map).append
33
- end
34
-
35
- # Side load the given `path` at `type`, without first resolving the path. This still respects
36
- # 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.
37
11
  #
38
- # @param path [String]
39
- # @param type [Symbol] :js or :css
40
- def append!(path, type)
41
- return if Proscenium::Current.loaded[type].include?(path)
42
-
43
- Proscenium::Current.loaded[type] << log(path)
44
- end
45
-
46
- def log(value)
47
- ActiveSupport::Notifications.instrument('sideload.proscenium', identifier: value)
48
-
49
- value
50
- end
51
- end
52
-
53
- # @param path [Pathname, String] The path of the file to be side loaded.
54
- # @param extension_map [Hash] File extensions to side load.
55
- def initialize(path, extension_map = EXTENSION_MAP)
56
- @path = (path.is_a?(Pathname) ? path : Rails.root.join(path)).sub_ext('')
57
- @extension_map = extension_map
58
-
59
- Proscenium::Current.loaded ||= EXTENSIONS.index_with { |_e| Set.new }
60
- end
61
-
62
- def append
63
- @extension_map.filter_map do |ext, type|
64
- next unless (resolved_path = resolve_path(path.sub_ext(ext)))
65
-
66
- # Make sure path is not already side loaded.
67
- unless Proscenium::Current.loaded[type].include?(resolved_path)
68
- 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
69
21
  end
70
-
71
- resolved_path
72
22
  end
73
23
  end
74
-
75
- private
76
-
77
- def log(...)
78
- self.class.log(...)
79
- end
80
-
81
- def resolve_path(path)
82
- path.exist? ? Utils.resolve_path(path.to_s) : nil
83
- end
84
24
  end
85
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.10.0'
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