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.
- checksums.yaml +4 -4
- data/README.md +451 -65
- data/lib/proscenium/builder.rb +144 -0
- 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 +20 -12
- 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/stimulus-loading.js +83 -0
- data/lib/proscenium/libs/test.js +1 -0
- data/lib/proscenium/log_subscriber.rb +1 -2
- data/lib/proscenium/middleware/base.rb +8 -8
- 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 +19 -4
- data/lib/proscenium/{side_load/monkey.rb → monkey.rb} +24 -12
- data/lib/proscenium/phlex/{resolve_css_modules.rb → css_modules.rb} +28 -16
- data/lib/proscenium/phlex/react_component.rb +27 -64
- data/lib/proscenium/phlex.rb +11 -30
- data/lib/proscenium/railtie.rb +49 -41
- data/lib/proscenium/react_componentable.rb +95 -0
- data/lib/proscenium/resolver.rb +37 -0
- data/lib/proscenium/side_load.rb +13 -72
- 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 -28
- data/lib/proscenium/view_component/sideload.rb +4 -0
- data/lib/proscenium/view_component.rb +8 -31
- data/lib/proscenium.rb +23 -68
- metadata +25 -59
- 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/esbuild/golib.rb +0 -97
- data/lib/proscenium/esbuild.rb +0 -32
- data/lib/proscenium/phlex/component_concerns.rb +0 -27
- 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 -25
- data/lib/proscenium/view_component/tag_builder.rb +0 -23
data/lib/proscenium/railtie.rb
CHANGED
@@ -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
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
|
33
|
-
|
34
|
-
#
|
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
|
-
#
|
37
|
-
#
|
38
|
-
#
|
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
|
-
#
|
42
|
-
#
|
43
|
-
#
|
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
|
-
|
48
|
-
|
49
|
-
|
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,
|
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.
|
59
|
-
|
60
|
-
|
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
|
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
|
data/lib/proscenium/side_load.rb
CHANGED
@@ -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
|
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
|
-
#
|
30
|
-
|
31
|
-
|
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
|
-
#
|
38
|
-
#
|
39
|
-
def
|
40
|
-
return if Proscenium
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
data/lib/proscenium/version.rb
CHANGED
@@ -1,35 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
# Renders
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
# component
|
11
|
-
|
12
|
-
|
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
|
-
|
14
|
+
include ReactComponentable
|
18
15
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
@@ -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 :
|
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
|
-
|
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 :
|
24
|
+
attr_accessor :abstract_class
|
28
25
|
|
29
26
|
def inherited(child)
|
30
|
-
child.
|
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
|
-
|
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 :
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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'
|