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.
- checksums.yaml +4 -4
- data/README.md +207 -45
- data/lib/proscenium/builder.rb +41 -19
- data/lib/proscenium/css_module/path.rb +31 -0
- data/lib/proscenium/css_module/transformer.rb +82 -0
- data/lib/proscenium/css_module.rb +12 -25
- data/lib/proscenium/ensure_loaded.rb +27 -0
- data/lib/proscenium/ext/proscenium +0 -0
- data/lib/proscenium/ext/proscenium.h +3 -2
- data/lib/proscenium/helper.rb +85 -0
- data/lib/proscenium/importer.rb +110 -0
- data/lib/proscenium/libs/react-manager/index.jsx +101 -0
- data/lib/proscenium/libs/react-manager/react.js +2 -0
- data/lib/proscenium/libs/test.js +1 -0
- data/lib/proscenium/middleware/base.rb +7 -7
- data/lib/proscenium/middleware/engines.rb +37 -0
- data/lib/proscenium/middleware/esbuild.rb +3 -5
- data/lib/proscenium/middleware/runtime.rb +18 -0
- data/lib/proscenium/middleware.rb +14 -4
- data/lib/proscenium/{side_load/monkey.rb → monkey.rb} +19 -15
- data/lib/proscenium/phlex/{resolve_css_modules.rb → css_modules.rb} +28 -16
- data/lib/proscenium/phlex/react_component.rb +26 -27
- data/lib/proscenium/phlex.rb +11 -30
- data/lib/proscenium/railtie.rb +44 -46
- data/lib/proscenium/react_componentable.rb +95 -0
- data/lib/proscenium/resolver.rb +37 -0
- data/lib/proscenium/side_load.rb +13 -73
- data/lib/proscenium/source_path.rb +15 -0
- data/lib/proscenium/templates/rescues/build_error.html.erb +30 -0
- data/lib/proscenium/utils.rb +13 -0
- data/lib/proscenium/version.rb +1 -1
- data/lib/proscenium/view_component/css_modules.rb +11 -0
- data/lib/proscenium/view_component/react_component.rb +15 -15
- data/lib/proscenium/view_component/sideload.rb +4 -0
- data/lib/proscenium/view_component.rb +8 -38
- data/lib/proscenium.rb +22 -68
- metadata +23 -30
- data/lib/proscenium/componentable.rb +0 -63
- data/lib/proscenium/css_module/class_names_resolver.rb +0 -66
- data/lib/proscenium/css_module/resolver.rb +0 -76
- data/lib/proscenium/current.rb +0 -9
- data/lib/proscenium/phlex/component_concerns.rb +0 -9
- data/lib/proscenium/phlex/page.rb +0 -62
- data/lib/proscenium/side_load/ensure_loaded.rb +0 -25
- data/lib/proscenium/side_load/helper.rb +0 -41
- 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::
|
5
|
-
|
4
|
+
module Phlex::CssModules
|
5
|
+
include Proscenium::CssModule
|
6
6
|
|
7
|
-
|
8
|
-
|
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.
|
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
|
-
|
55
|
-
|
56
|
-
attributes[:class] =
|
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
|
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
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
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
|
-
#
|
30
|
-
|
31
|
-
|
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
|
data/lib/proscenium/phlex.rb
CHANGED
@@ -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 :
|
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 :
|
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
|
-
|
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 :
|
33
|
+
attr_accessor :abstract_class
|
45
34
|
|
46
35
|
def inherited(child)
|
47
|
-
|
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
|
data/lib/proscenium/railtie.rb
CHANGED
@@ -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 =
|
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
|
-
#
|
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
|
-
#
|
47
|
-
#
|
48
|
-
#
|
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
|
-
#
|
52
|
-
#
|
53
|
-
#
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
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,
|
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.
|
69
|
-
|
70
|
-
|
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
|
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
|
data/lib/proscenium/side_load.rb
CHANGED
@@ -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
|
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
|
-
#
|
31
|
-
|
32
|
-
|
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
|
-
#
|
39
|
-
#
|
40
|
-
def
|
41
|
-
return if Proscenium
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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
|
data/lib/proscenium/version.rb
CHANGED