proscenium 0.6.0-x86_64-darwin → 0.7.0-x86_64-darwin
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 +128 -107
- data/bin/proscenium +0 -0
- data/bin/proscenium.h +109 -0
- data/config/routes.rb +0 -3
- data/lib/proscenium/css_module/class_names_resolver.rb +66 -0
- data/lib/proscenium/css_module/resolver.rb +76 -0
- data/lib/proscenium/css_module.rb +18 -39
- data/lib/proscenium/esbuild/golib.rb +97 -0
- data/lib/proscenium/esbuild.rb +32 -0
- data/lib/proscenium/helper.rb +0 -23
- data/lib/proscenium/log_subscriber.rb +26 -0
- data/lib/proscenium/middleware/base.rb +28 -36
- data/lib/proscenium/middleware/esbuild.rb +18 -44
- data/lib/proscenium/middleware/url.rb +1 -6
- data/lib/proscenium/middleware.rb +12 -16
- data/lib/proscenium/phlex/component_concerns.rb +27 -0
- data/lib/proscenium/phlex/page.rb +62 -0
- data/lib/proscenium/phlex/react_component.rb +52 -8
- data/lib/proscenium/phlex/resolve_css_modules.rb +67 -0
- data/lib/proscenium/phlex.rb +34 -33
- data/lib/proscenium/railtie.rb +41 -67
- data/lib/proscenium/side_load/ensure_loaded.rb +25 -0
- data/lib/proscenium/side_load/helper.rb +25 -0
- data/lib/proscenium/side_load/monkey.rb +48 -0
- data/lib/proscenium/side_load.rb +58 -52
- data/lib/proscenium/version.rb +1 -1
- data/lib/proscenium/view_component/react_component.rb +14 -0
- data/lib/proscenium/view_component.rb +28 -18
- data/lib/proscenium.rb +79 -2
- metadata +35 -75
- data/app/channels/proscenium/connection.rb +0 -13
- data/app/channels/proscenium/reload_channel.rb +0 -9
- data/bin/esbuild +0 -0
- data/bin/lightningcss +0 -0
- data/lib/proscenium/compiler.js +0 -84
- data/lib/proscenium/compilers/esbuild/argument_error.js +0 -24
- data/lib/proscenium/compilers/esbuild/compile_error.js +0 -148
- data/lib/proscenium/compilers/esbuild/css/postcss.js +0 -67
- data/lib/proscenium/compilers/esbuild/css_plugin.js +0 -172
- data/lib/proscenium/compilers/esbuild/env_plugin.js +0 -46
- data/lib/proscenium/compilers/esbuild/http_bundle_plugin.js +0 -53
- data/lib/proscenium/compilers/esbuild/import_map/parser.js +0 -178
- data/lib/proscenium/compilers/esbuild/import_map/read.js +0 -64
- data/lib/proscenium/compilers/esbuild/import_map/resolver.js +0 -95
- data/lib/proscenium/compilers/esbuild/import_map/utils.js +0 -25
- data/lib/proscenium/compilers/esbuild/resolve_plugin.js +0 -207
- data/lib/proscenium/compilers/esbuild/setup_plugin.js +0 -45
- data/lib/proscenium/compilers/esbuild/solidjs_plugin.js +0 -24
- data/lib/proscenium/compilers/esbuild.bench.js +0 -14
- data/lib/proscenium/compilers/esbuild.js +0 -179
- data/lib/proscenium/link_to_helper.rb +0 -40
- data/lib/proscenium/middleware/lightningcss.rb +0 -64
- data/lib/proscenium/middleware/outside_root.rb +0 -26
- data/lib/proscenium/middleware/runtime.rb +0 -22
- data/lib/proscenium/middleware/static.rb +0 -14
- data/lib/proscenium/phlex/component.rb +0 -9
- data/lib/proscenium/precompile.rb +0 -31
- data/lib/proscenium/runtime/auto_reload.js +0 -40
- data/lib/proscenium/runtime/react_shim/index.js +0 -1
- data/lib/proscenium/runtime/react_shim/package.json +0 -5
- data/lib/proscenium/utils.js +0 -12
- data/lib/tasks/assets.rake +0 -19
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Proscenium
|
4
|
+
module Phlex::ResolveCssModules
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
class_methods do
|
8
|
+
attr_accessor :side_load_cache
|
9
|
+
end
|
10
|
+
|
11
|
+
def before_template
|
12
|
+
self.class.side_load_cache ||= Set.new
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
# Resolve and side load any CSS modules in the "class" attributes, where a CSS module is a class
|
17
|
+
# name beginning with a `@`. The class name is resolved to a CSS module name based on the file
|
18
|
+
# system path of the Phlex class, and any CSS file is side loaded.
|
19
|
+
#
|
20
|
+
# For example, the following will side load the CSS module file at
|
21
|
+
# app/components/user/component.module.css, and add the CSS Module name `user_name` to the
|
22
|
+
# <div>.
|
23
|
+
#
|
24
|
+
# # app/components/user/component.rb
|
25
|
+
# class User::Component < Proscenium::Phlex
|
26
|
+
# def template
|
27
|
+
# div class: :@user_name do
|
28
|
+
# 'Joel Moss'
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# Additionally, any class name containing a `/` is resolved as a CSS module path. Allowing you
|
34
|
+
# to use the same syntax as a CSS module, but without the need to manually import the CSS file.
|
35
|
+
#
|
36
|
+
# For example, the following will side load the CSS module file at /lib/users.module.css, and
|
37
|
+
# add the CSS Module name `name` to the <div>.
|
38
|
+
#
|
39
|
+
# class User::Component < Proscenium::Phlex
|
40
|
+
# def template
|
41
|
+
# div class: '/lib/users@name' do
|
42
|
+
# 'Joel Moss'
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
#
|
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
|
+
# @raise [Proscenium::CssModule::Resolver::NotFound] If a CSS module file is not found for the
|
51
|
+
# Phlex class file path.
|
52
|
+
def process_attributes(**attributes)
|
53
|
+
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
|
57
|
+
end
|
58
|
+
|
59
|
+
attributes
|
60
|
+
end
|
61
|
+
|
62
|
+
def after_template
|
63
|
+
super
|
64
|
+
self.class.side_load_cache&.each { |path| SideLoad.append! path, :css }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/proscenium/phlex.rb
CHANGED
@@ -1,60 +1,61 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'phlex'
|
3
|
+
require 'phlex-rails'
|
4
4
|
|
5
5
|
module Proscenium
|
6
|
-
class Phlex < ::Phlex::
|
6
|
+
class Phlex < ::Phlex::HTML
|
7
7
|
extend ActiveSupport::Autoload
|
8
|
+
include Proscenium::CssModule
|
8
9
|
|
9
|
-
autoload :
|
10
|
+
autoload :Page
|
10
11
|
autoload :ReactComponent
|
12
|
+
autoload :ResolveCssModules
|
13
|
+
autoload :ComponentConcerns
|
11
14
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
+
extend ::Phlex::Rails::HelperMacros
|
16
|
+
include ::Phlex::Rails::Helpers::JavaScriptIncludeTag
|
17
|
+
include ::Phlex::Rails::Helpers::StyleSheetLinkTag
|
15
18
|
|
16
|
-
|
17
|
-
|
19
|
+
define_output_helper :side_load_stylesheets
|
20
|
+
define_output_helper :side_load_javascripts
|
21
|
+
|
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
|
+
module Sideload
|
26
|
+
def before_template
|
27
|
+
klass = self.class
|
18
28
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
@_target << output
|
23
|
-
end
|
29
|
+
if !klass.abstract_class && respond_to?(:side_load, true)
|
30
|
+
side_load
|
31
|
+
klass = klass.superclass
|
24
32
|
end
|
25
|
-
end
|
26
|
-
end
|
27
33
|
|
28
|
-
|
29
|
-
|
30
|
-
|
34
|
+
while !klass.abstract_class && klass.respond_to?(:path) && klass.path
|
35
|
+
Proscenium::SideLoad.append klass.path
|
36
|
+
klass = klass.superclass
|
37
|
+
end
|
31
38
|
|
32
39
|
super
|
33
40
|
end
|
34
41
|
end
|
35
42
|
|
36
43
|
class << self
|
37
|
-
attr_accessor :path
|
44
|
+
attr_accessor :path, :abstract_class
|
38
45
|
|
39
46
|
def inherited(child)
|
40
|
-
|
41
|
-
|
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
|
42
54
|
|
43
|
-
child.prepend Sideload
|
44
|
-
child.include Helpers
|
55
|
+
child.prepend Sideload if Rails.application.config.proscenium.side_load
|
45
56
|
|
46
57
|
super
|
47
58
|
end
|
48
59
|
end
|
49
|
-
|
50
|
-
def css_module(name)
|
51
|
-
cssm.class_names!(name).join ' '
|
52
|
-
end
|
53
|
-
|
54
|
-
private
|
55
|
-
|
56
|
-
def cssm
|
57
|
-
@cssm ||= Proscenium::CssModule.new(self.class.path)
|
58
|
-
end
|
59
60
|
end
|
60
61
|
end
|
data/lib/proscenium/railtie.rb
CHANGED
@@ -1,23 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'rails'
|
4
|
-
require '
|
5
|
-
require 'listen'
|
4
|
+
require 'proscenium/log_subscriber'
|
6
5
|
|
7
6
|
ENV['RAILS_ENV'] = Rails.env
|
8
7
|
|
9
8
|
module Proscenium
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
esbuild: '/{config,app,lib,node_modules}/**.{js,mjs,jsx,css}',
|
16
|
-
runtime: '/proscenium-runtime/**.{js,jsx}',
|
17
|
-
url: %r{^/url:https?%3A%2F%2F},
|
18
|
-
outsideRoot: '/**/*.{js,jsx,mjs,css}'
|
9
|
+
FILE_EXTENSIONS = ['js', 'mjs', 'jsx', 'css', 'js.map', 'mjs.map', 'jsx.map', 'css.map'].freeze
|
10
|
+
|
11
|
+
MIDDLEWARE_GLOB_TYPES = {
|
12
|
+
application: "/**.{#{FILE_EXTENSIONS.join(',')}}",
|
13
|
+
url: %r{^/https?%3A%2F%2F}
|
19
14
|
}.freeze
|
20
15
|
|
16
|
+
APPLICATION_INCLUDE_PATHS = ['config', 'app/views', 'lib', 'node_modules'].freeze
|
17
|
+
|
21
18
|
class << self
|
22
19
|
def config
|
23
20
|
@config ||= Railtie.config.proscenium
|
@@ -31,21 +28,29 @@ module Proscenium
|
|
31
28
|
config.proscenium.side_load = true
|
32
29
|
config.proscenium.cache_query_string = Rails.env.production? && ENV.fetch('REVISION', nil)
|
33
30
|
config.proscenium.cache_max_age = 2_592_000 # 30 days
|
34
|
-
config.proscenium.
|
35
|
-
|
36
|
-
|
31
|
+
config.proscenium.include_paths = Set.new(APPLICATION_INCLUDE_PATHS)
|
32
|
+
|
33
|
+
# A hash of gems that can be side loaded. Assets from gems listed here can be side loaded.
|
34
|
+
#
|
35
|
+
# Because side loading uses URL paths, any gem dependencies that side load assets will fail,
|
36
|
+
# because the URL path will be relative to the application's root, and not the gem's root. By
|
37
|
+
# specifying a list of gems that can be side loaded, Proscenium will be able to resolve the URL
|
38
|
+
# path to the gem's root, and side load the asset.
|
39
|
+
#
|
40
|
+
# Side loading gems rely on NPM and a package.json file in the gem root. This ensures that any
|
41
|
+
# dependencies are resolved correctly. This is required even if your gem has no package
|
42
|
+
# dependencies.
|
43
|
+
#
|
44
|
+
# Example:
|
45
|
+
# config.proscenium.side_load_gems['mygem'] = {
|
46
|
+
# root: gem_root,
|
47
|
+
# package_name: 'mygem'
|
48
|
+
# }
|
49
|
+
config.proscenium.side_load_gems = {}
|
37
50
|
|
38
51
|
initializer 'proscenium.configuration' do |app|
|
39
52
|
options = app.config.proscenium
|
40
|
-
|
41
|
-
options.glob_types = DEFAULT_GLOB_TYPES if options.glob_types.blank?
|
42
|
-
options.auto_reload_paths.filter! { |path| Dir.exist? path }
|
43
|
-
options.cable_mount_path ||= '/proscenium-cable'
|
44
|
-
options.cable_logger ||= Rails.logger
|
45
|
-
end
|
46
|
-
|
47
|
-
initializer 'proscenium.side_load' do |_app|
|
48
|
-
Proscenium::Current.loaded ||= SideLoad::EXTENSIONS.to_h { |e| [e, Set[]] }
|
53
|
+
options.include_paths = Set.new(APPLICATION_INCLUDE_PATHS) if options.include_paths.blank?
|
49
54
|
end
|
50
55
|
|
51
56
|
initializer 'proscenium.middleware' do |app|
|
@@ -54,57 +59,26 @@ module Proscenium
|
|
54
59
|
app.middleware.insert_after ActionDispatch::Static, Rack::ConditionalGet
|
55
60
|
end
|
56
61
|
|
57
|
-
initializer 'proscenium.
|
58
|
-
|
59
|
-
|
62
|
+
initializer 'proscenium.side_loading' do |app|
|
63
|
+
if app.config.proscenium.side_load
|
64
|
+
Proscenium::Current.loaded ||= SideLoad::EXTENSIONS.to_h { |e| [e, Set.new] }
|
65
|
+
|
66
|
+
ActiveSupport.on_load(:action_view) do
|
67
|
+
ActionView::Base.include Proscenium::SideLoad::Helper
|
60
68
|
|
61
|
-
if Rails.application.config.proscenium.side_load
|
62
69
|
ActionView::TemplateRenderer.prepend SideLoad::Monkey::TemplateRenderer
|
70
|
+
ActionView::PartialRenderer.prepend SideLoad::Monkey::PartialRenderer
|
63
71
|
end
|
64
72
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
config.after_initialize do
|
70
|
-
next unless config.proscenium.auto_reload
|
71
|
-
|
72
|
-
@listener = Listen.to(*config.proscenium.auto_reload_paths,
|
73
|
-
only: config.proscenium.auto_reload_extensions) do |mod, add, rem|
|
74
|
-
Proscenium::Railtie.websocket&.broadcast('reload', {
|
75
|
-
modified: mod,
|
76
|
-
removed: rem,
|
77
|
-
added: add
|
78
|
-
})
|
73
|
+
ActiveSupport.on_load(:action_controller) do
|
74
|
+
ActionController::Base.include Proscenium::SideLoad::EnsureLoaded
|
75
|
+
end
|
79
76
|
end
|
80
|
-
|
81
|
-
@listener.start
|
82
|
-
end
|
83
|
-
|
84
|
-
at_exit do
|
85
|
-
@listener&.stop
|
86
77
|
end
|
87
78
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
return unless config.proscenium.auto_reload
|
92
|
-
|
93
|
-
cable = ActionCable::Server::Configuration.new
|
94
|
-
cable.cable = { adapter: 'async' }.with_indifferent_access
|
95
|
-
cable.mount_path = config.proscenium.cable_mount_path
|
96
|
-
cable.connection_class = -> { Proscenium::Connection }
|
97
|
-
cable.logger = config.proscenium.cable_logger
|
98
|
-
|
99
|
-
@websocket ||= ActionCable::Server::Base.new(config: cable)
|
100
|
-
end
|
101
|
-
|
102
|
-
def websocket_mount_path
|
103
|
-
"#{mounted_path}#{config.proscenium.cable_mount_path}" if websocket
|
104
|
-
end
|
105
|
-
|
106
|
-
def mounted_path
|
107
|
-
Proscenium::Railtie.routes.find_script_name({})
|
79
|
+
initializer 'proscenium.helper' do
|
80
|
+
ActiveSupport.on_load(:action_view) do
|
81
|
+
ActionView::Base.include Proscenium::Helper
|
108
82
|
end
|
109
83
|
end
|
110
84
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Proscenium::SideLoad
|
4
|
+
module EnsureLoaded
|
5
|
+
def self.included(child)
|
6
|
+
child.class_eval do
|
7
|
+
append_after_action do
|
8
|
+
if Proscenium::Current.loaded
|
9
|
+
if Proscenium::Current.loaded[:js].present?
|
10
|
+
raise NotIncludedError, 'There are javascripts to be side loaded, but they have not ' \
|
11
|
+
'been included. Did you forget to add the ' \
|
12
|
+
'`#side_load_javascripts` helper in your views?'
|
13
|
+
end
|
14
|
+
|
15
|
+
if Proscenium::Current.loaded[:css].present?
|
16
|
+
raise NotIncludedError, 'There are stylesheets to be side loaded, but they have not ' \
|
17
|
+
'been included. Did you forget to add the ' \
|
18
|
+
'`#side_load_stylesheets` helper in your views?'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Proscenium
|
4
|
+
module SideLoad::Helper
|
5
|
+
def side_load_stylesheets
|
6
|
+
return unless Proscenium::Current.loaded
|
7
|
+
|
8
|
+
out = []
|
9
|
+
Proscenium::Current.loaded[:css].delete_if do |path|
|
10
|
+
out << stylesheet_link_tag(path)
|
11
|
+
end
|
12
|
+
out.join("\n").html_safe
|
13
|
+
end
|
14
|
+
|
15
|
+
def side_load_javascripts(**options)
|
16
|
+
return unless Proscenium::Current.loaded
|
17
|
+
|
18
|
+
out = []
|
19
|
+
Proscenium::Current.loaded[:js].delete_if do |path|
|
20
|
+
out << javascript_include_tag(path, options)
|
21
|
+
end
|
22
|
+
out.join("\n").html_safe
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Proscenium::SideLoad
|
4
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
5
|
+
module Monkey
|
6
|
+
module TemplateRenderer
|
7
|
+
private
|
8
|
+
|
9
|
+
def render_template(view, template, layout_name, locals)
|
10
|
+
layout = find_layout(layout_name, locals.keys, [formats.first])
|
11
|
+
renderable = template.instance_variable_get(:@renderable)
|
12
|
+
|
13
|
+
if Object.const_defined?(:ViewComponent) &&
|
14
|
+
template.is_a?(ActionView::Template::Renderable) &&
|
15
|
+
renderable.class < ::ViewComponent::Base && renderable.class.format == :html
|
16
|
+
# Side load controller rendered ViewComponent
|
17
|
+
Proscenium::SideLoad.append "app/views/#{layout.virtual_path}" if layout
|
18
|
+
Proscenium::SideLoad.append "app/views/#{renderable.virtual_path}"
|
19
|
+
elsif template.respond_to?(:virtual_path) &&
|
20
|
+
template.respond_to?(:type) && template.type == :html
|
21
|
+
# Side load regular view template.
|
22
|
+
Proscenium::SideLoad.append "app/views/#{layout.virtual_path}" if layout
|
23
|
+
|
24
|
+
# Try side loading the variant template
|
25
|
+
if template.respond_to?(:variant) && template.variant
|
26
|
+
Proscenium::SideLoad.append "app/views/#{template.virtual_path}+#{template.variant}"
|
27
|
+
end
|
28
|
+
|
29
|
+
# The variant template may not exist (above), so we try the regular non-variant path.
|
30
|
+
Proscenium::SideLoad.append "app/views/#{template.virtual_path}"
|
31
|
+
end
|
32
|
+
|
33
|
+
super
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
module PartialRenderer
|
38
|
+
private
|
39
|
+
|
40
|
+
def build_rendered_template(content, template)
|
41
|
+
path = Rails.root.join('app', 'views', template.virtual_path)
|
42
|
+
cssm = Proscenium::CssModule::Resolver.new(path)
|
43
|
+
super cssm.compile_class_names(content), template
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
48
|
+
end
|
data/lib/proscenium/side_load.rb
CHANGED
@@ -1,72 +1,78 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
4
3
|
module Proscenium
|
5
|
-
|
6
|
-
|
7
|
-
EXTENSIONS = %i[js css].freeze
|
4
|
+
class SideLoad
|
5
|
+
extend ActiveSupport::Autoload
|
8
6
|
|
9
|
-
|
7
|
+
NotIncludedError = Class.new(StandardError)
|
10
8
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
def append(path, *extensions)
|
15
|
-
Proscenium::Current.loaded ||= EXTENSIONS.to_h { |e| [e, Set[]] }
|
9
|
+
autoload :Monkey
|
10
|
+
autoload :Helper
|
11
|
+
autoload :EnsureLoaded
|
16
12
|
|
17
|
-
|
18
|
-
|
19
|
-
end
|
13
|
+
EXTENSIONS = %i[js css].freeze
|
14
|
+
EXTENSION_MAP = { '.css' => :css, '.js' => :js }.freeze
|
20
15
|
|
21
|
-
|
16
|
+
attr_reader :path
|
22
17
|
|
23
|
-
|
24
|
-
|
25
|
-
|
18
|
+
class << self
|
19
|
+
# Side load the given asset `path`, by appending it to `Proscenium::Current.loaded`, which is a
|
20
|
+
# Set of 'js' and 'css' asset paths. This is idempotent, so side loading will never include
|
21
|
+
# duplicates.
|
22
|
+
#
|
23
|
+
# @return [Array] appended URL paths
|
24
|
+
def append(path, extension_map = EXTENSION_MAP)
|
25
|
+
new(path, extension_map).append
|
26
|
+
end
|
26
27
|
|
27
|
-
|
28
|
-
|
28
|
+
# Side load the given `path` at `type`, without first resolving the path. This still respects
|
29
|
+
# idempotency of `Proscenium::Current.loaded`.
|
30
|
+
#
|
31
|
+
# @param path [String]
|
32
|
+
# @param type [Symbol] :js or :css
|
33
|
+
def append!(path, type)
|
34
|
+
return if Proscenium::Current.loaded[type].include?(path)
|
29
35
|
|
30
|
-
Proscenium::Current.loaded[
|
31
|
-
loaded_types << ext
|
36
|
+
Proscenium::Current.loaded[type] << log(path)
|
32
37
|
end
|
33
38
|
|
34
|
-
|
35
|
-
|
39
|
+
def log(value)
|
40
|
+
ActiveSupport::Notifications.instrument('sideload.proscenium', identifier: value)
|
41
|
+
|
42
|
+
value
|
36
43
|
end
|
37
44
|
end
|
38
45
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
Proscenium::SideLoad.append "app/views/#{layout.virtual_path}" if layout
|
56
|
-
|
57
|
-
# Try side loading the variant template
|
58
|
-
if template.respond_to?(:variant) && template.variant
|
59
|
-
Proscenium::SideLoad.append "app/views/#{template.virtual_path}+#{template.variant}"
|
60
|
-
end
|
61
|
-
|
62
|
-
# The variant template may not exist (above), so we try the regular non-variant path.
|
63
|
-
Proscenium::SideLoad.append "app/views/#{template.virtual_path}"
|
64
|
-
end
|
65
|
-
|
66
|
-
super
|
46
|
+
# @param path [Pathname, String] The path of the file to be side loaded.
|
47
|
+
# @param extension_map [Hash] File extensions to side load.
|
48
|
+
def initialize(path, extension_map = EXTENSION_MAP)
|
49
|
+
@path = (path.is_a?(Pathname) ? path : Rails.root.join(path)).sub_ext('')
|
50
|
+
@extension_map = extension_map
|
51
|
+
|
52
|
+
Proscenium::Current.loaded ||= EXTENSIONS.index_with { |_e| Set.new }
|
53
|
+
end
|
54
|
+
|
55
|
+
def append
|
56
|
+
@extension_map.filter_map do |ext, type|
|
57
|
+
next unless (resolved_path = resolve_path(path.sub_ext(ext)))
|
58
|
+
|
59
|
+
# Make sure path is not already side loaded.
|
60
|
+
unless Proscenium::Current.loaded[type].include?(resolved_path)
|
61
|
+
Proscenium::Current.loaded[type] << log(resolved_path)
|
67
62
|
end
|
63
|
+
|
64
|
+
resolved_path
|
68
65
|
end
|
69
66
|
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def log(...)
|
71
|
+
self.class.log(...)
|
72
|
+
end
|
73
|
+
|
74
|
+
def resolve_path(path)
|
75
|
+
path.exist? ? Utils.resolve_path(path.to_s) : nil
|
76
|
+
end
|
70
77
|
end
|
71
78
|
end
|
72
|
-
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
data/lib/proscenium/version.rb
CHANGED
@@ -1,10 +1,24 @@
|
|
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
|
+
#
|
3
14
|
class Proscenium::ViewComponent::ReactComponent < Proscenium::ViewComponent
|
15
|
+
self.abstract_class = true
|
16
|
+
|
4
17
|
attr_accessor :props, :lazy
|
5
18
|
|
6
19
|
# @param props: [Hash]
|
7
20
|
# @param lazy: [Boolean] Lazy load the component using IntersectionObserver. Default: true.
|
21
|
+
# @param [Block]
|
8
22
|
def initialize(props: {}, lazy: true)
|
9
23
|
@props = props
|
10
24
|
@lazy = lazy
|
@@ -4,37 +4,47 @@ require 'view_component'
|
|
4
4
|
|
5
5
|
class Proscenium::ViewComponent < ViewComponent::Base
|
6
6
|
extend ActiveSupport::Autoload
|
7
|
+
include Proscenium::CssModule
|
7
8
|
|
8
9
|
autoload :TagBuilder
|
9
10
|
autoload :ReactComponent
|
10
11
|
|
11
|
-
|
12
|
-
|
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
|
+
module Sideload
|
15
|
+
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
|
21
|
+
|
22
|
+
super
|
23
|
+
end
|
13
24
|
end
|
14
25
|
|
15
|
-
|
16
|
-
|
17
|
-
end
|
26
|
+
class << self
|
27
|
+
attr_accessor :path, :abstract_class
|
18
28
|
|
19
|
-
|
20
|
-
|
21
|
-
|
29
|
+
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
|
22
35
|
|
23
|
-
|
36
|
+
child.prepend Sideload if Rails.application.config.proscenium.side_load
|
24
37
|
|
25
|
-
|
26
|
-
|
27
|
-
def side_load_assets
|
28
|
-
Proscenium::SideLoad.append asset_path if Rails.application.config.proscenium.side_load
|
38
|
+
super
|
39
|
+
end
|
29
40
|
end
|
30
41
|
|
31
|
-
|
32
|
-
|
42
|
+
# @override Auto compilation of class names to css modules.
|
43
|
+
def render_in(...)
|
44
|
+
cssm.compile_class_names(super(...))
|
33
45
|
end
|
34
46
|
|
35
|
-
|
36
|
-
@cssm ||= Proscenium::CssModule.new(asset_path)
|
37
|
-
end
|
47
|
+
private
|
38
48
|
|
39
49
|
# Overrides ActionView::Helpers::TagHelper::TagBuilder, allowing us to intercept the
|
40
50
|
# `css_module` option from the HTML options argument of the `tag` and `content_tag` helpers, and
|