proscenium 0.6.0-arm64-darwin → 0.7.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 +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
@@ -1,7 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
module Proscenium::CssModule
|
4
|
+
extend ActiveSupport::Autoload
|
5
|
+
|
6
|
+
class StylesheetNotFound < StandardError
|
5
7
|
def initialize(pathname)
|
6
8
|
@pathname = pathname
|
7
9
|
super
|
@@ -12,53 +14,30 @@ class Proscenium::CssModule
|
|
12
14
|
end
|
13
15
|
end
|
14
16
|
|
15
|
-
|
16
|
-
|
17
|
-
@css_module_path = "#{path}.module.css"
|
18
|
-
end
|
17
|
+
autoload :ClassNamesResolver
|
18
|
+
autoload :Resolver # deprecated
|
19
19
|
|
20
|
-
#
|
21
|
-
# returns the content with said CSS Modules replaced with the compiled class names.
|
20
|
+
# Like `css_modules`, but will raise if the stylesheet cannot be found.
|
22
21
|
#
|
23
|
-
#
|
24
|
-
|
25
|
-
|
26
|
-
doc = Nokogiri::HTML::DocumentFragment.parse(content)
|
27
|
-
|
28
|
-
return content if (modules = doc.css('[class*="@"]')).empty?
|
29
|
-
|
30
|
-
modules.each do |ele|
|
31
|
-
classes = ele.classes.map { |cls| cls.starts_with?('@') ? class_names!(cls[1..]) : cls }
|
32
|
-
ele['class'] = classes.join(' ')
|
33
|
-
end
|
34
|
-
|
35
|
-
doc.to_html.html_safe
|
36
|
-
end
|
37
|
-
|
38
|
-
# @returns [Array] of class names generated from the given CSS module `names`.
|
39
|
-
def class_names(*names)
|
40
|
-
side_load_css_module
|
41
|
-
names.flatten.compact.map { |name| "#{name.to_s.camelize(:lower)}#{hash}" }
|
22
|
+
# @param name [Array, String]
|
23
|
+
def css_module!(names)
|
24
|
+
cssm.class_names!(names).join ' '
|
42
25
|
end
|
43
26
|
|
44
|
-
#
|
27
|
+
# Accepts one or more CSS class names, and transforms them into CSS module names.
|
45
28
|
#
|
46
|
-
# @
|
47
|
-
def
|
48
|
-
|
49
|
-
|
50
|
-
class_names(...)
|
29
|
+
# @param name [Array, String]
|
30
|
+
def css_module(names)
|
31
|
+
cssm.class_names(names).join ' '
|
51
32
|
end
|
52
33
|
|
53
34
|
private
|
54
35
|
|
55
|
-
def
|
56
|
-
|
36
|
+
def path
|
37
|
+
self.class.path
|
57
38
|
end
|
58
39
|
|
59
|
-
def
|
60
|
-
|
61
|
-
|
62
|
-
Proscenium::SideLoad.append "#{@path}.module", :css
|
40
|
+
def cssm
|
41
|
+
@cssm ||= Resolver.new(path)
|
63
42
|
end
|
64
43
|
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ffi'
|
4
|
+
require 'oj'
|
5
|
+
|
6
|
+
module Proscenium
|
7
|
+
class Esbuild::Golib
|
8
|
+
class Result < FFI::Struct
|
9
|
+
layout :success, :bool,
|
10
|
+
:response, :string
|
11
|
+
end
|
12
|
+
|
13
|
+
module Request
|
14
|
+
extend FFI::Library
|
15
|
+
ffi_lib Pathname.new(__dir__).join('../../../bin/proscenium').to_s
|
16
|
+
|
17
|
+
enum :environment, [:development, 1, :test, :production]
|
18
|
+
|
19
|
+
attach_function :build, [
|
20
|
+
:string, # path or entry point
|
21
|
+
:string, # root
|
22
|
+
:string, # base URL of the Rails app. eg. https://example.com
|
23
|
+
:environment, # Rails environment as a Symbol
|
24
|
+
:string, # path to import map, relative to root
|
25
|
+
:bool # debugging enabled?
|
26
|
+
], Result.by_value
|
27
|
+
|
28
|
+
attach_function :resolve, [
|
29
|
+
:string, # path or entry point
|
30
|
+
:string, # root
|
31
|
+
:environment, # Rails environment as a Symbol
|
32
|
+
:string # path to import map, relative to root
|
33
|
+
], Result.by_value
|
34
|
+
end
|
35
|
+
|
36
|
+
class BuildError < StandardError
|
37
|
+
attr_reader :error, :path
|
38
|
+
|
39
|
+
def initialize(path, error)
|
40
|
+
error = Oj.load(error, mode: :strict).deep_transform_keys(&:underscore)
|
41
|
+
|
42
|
+
super "Failed to build '#{path}' -- #{error['text']}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class ResolveError < StandardError
|
47
|
+
attr_reader :error_msg, :path
|
48
|
+
|
49
|
+
def initialize(path, error_msg)
|
50
|
+
super "Failed to resolve '#{path}' -- #{error_msg}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def initialize(root: nil, base_url: nil)
|
55
|
+
@root = root || Rails.root
|
56
|
+
@base_url = base_url
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.resolve(path)
|
60
|
+
new.resolve(path)
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.build(path)
|
64
|
+
new.build(path)
|
65
|
+
end
|
66
|
+
|
67
|
+
def build(path)
|
68
|
+
result = Request.build(path, @root.to_s, @base_url, Rails.env.to_sym, import_map,
|
69
|
+
Rails.env.development?)
|
70
|
+
raise BuildError.new(path, result[:response]) unless result[:success]
|
71
|
+
|
72
|
+
result[:response]
|
73
|
+
end
|
74
|
+
|
75
|
+
def resolve(path)
|
76
|
+
result = Request.resolve(path, @root.to_s, Rails.env.to_sym, import_map)
|
77
|
+
raise ResolveError.new(path, result[:response]) unless result[:success]
|
78
|
+
|
79
|
+
result[:response]
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def import_map
|
85
|
+
return unless (path = Rails.root&.join('config'))
|
86
|
+
|
87
|
+
if (json = path.join('import_map.json')).exist?
|
88
|
+
return json.relative_path_from(@root).to_s
|
89
|
+
end
|
90
|
+
if (js = path.join('import_map.js')).exist?
|
91
|
+
return js.relative_path_from(@root).to_s
|
92
|
+
end
|
93
|
+
|
94
|
+
nil
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Proscenium
|
4
|
+
class Esbuild
|
5
|
+
class CompileError < StandardError; end
|
6
|
+
|
7
|
+
extend ActiveSupport::Autoload
|
8
|
+
|
9
|
+
autoload :Golib
|
10
|
+
|
11
|
+
def self.build(...)
|
12
|
+
new(...).build
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(path, root:, base_url:)
|
16
|
+
@path = path
|
17
|
+
@root = root
|
18
|
+
@base_url = base_url
|
19
|
+
end
|
20
|
+
|
21
|
+
def build
|
22
|
+
Proscenium::Esbuild::Golib.new(root: @root, base_url: @base_url).build(@path)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def cache_query_string
|
28
|
+
q = Proscenium.config.cache_query_string
|
29
|
+
q ? "--cache-query-string #{q}" : nil
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/proscenium/helper.rb
CHANGED
@@ -15,28 +15,5 @@ module Proscenium
|
|
15
15
|
|
16
16
|
super
|
17
17
|
end
|
18
|
-
|
19
|
-
def side_load_stylesheets
|
20
|
-
return unless Proscenium::Current.loaded
|
21
|
-
|
22
|
-
Proscenium::Current.loaded[:css].map do |sheet|
|
23
|
-
stylesheet_link_tag(sheet, id: "_#{Digest::SHA1.hexdigest("/#{sheet}")[..7]}")
|
24
|
-
end.join("\n").html_safe
|
25
|
-
end
|
26
|
-
|
27
|
-
def side_load_javascripts(**options)
|
28
|
-
return unless Proscenium::Current.loaded
|
29
|
-
|
30
|
-
javascript_include_tag(*Proscenium::Current.loaded[:js], options)
|
31
|
-
end
|
32
|
-
|
33
|
-
def proscenium_dev
|
34
|
-
return unless Proscenium.config.auto_reload
|
35
|
-
|
36
|
-
javascript_tag %(
|
37
|
-
import autoReload from '/proscenium-runtime/auto_reload.js';
|
38
|
-
autoReload('#{Proscenium::Railtie.websocket_mount_path}');
|
39
|
-
), type: 'module', defer: true
|
40
|
-
end
|
41
18
|
end
|
42
19
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/log_subscriber'
|
4
|
+
|
5
|
+
module Proscenium
|
6
|
+
class LogSubscriber < ActiveSupport::LogSubscriber
|
7
|
+
def sideload(event)
|
8
|
+
info do
|
9
|
+
" [Proscenium] Side loaded #{event.payload[:identifier]}"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def build(event)
|
14
|
+
path = event.payload[:identifier]
|
15
|
+
path = path.start_with?(/https?%3A%2F%2F/) ? CGI.unescape(path) : path
|
16
|
+
|
17
|
+
info do
|
18
|
+
message = +"[Proscenium] Building #{path}"
|
19
|
+
message << " (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
|
20
|
+
message << "\n" if defined?(Rails.env) && Rails.env.development?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
Proscenium::LogSubscriber.attach_to :proscenium
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'open3'
|
4
|
+
require 'oj'
|
4
5
|
|
5
6
|
module Proscenium
|
6
7
|
class Middleware
|
@@ -9,7 +10,15 @@ module Proscenium
|
|
9
10
|
|
10
11
|
# Error when the result of the build returns an error. For example, when esbuild returns
|
11
12
|
# errors.
|
12
|
-
class CompileError < StandardError
|
13
|
+
class CompileError < StandardError
|
14
|
+
attr_reader :detail, :file
|
15
|
+
|
16
|
+
def initialize(args)
|
17
|
+
@detail = args[:detail]
|
18
|
+
@file = args[:file]
|
19
|
+
super "Failed to build '#{args[:file]}' -- #{detail}"
|
20
|
+
end
|
21
|
+
end
|
13
22
|
|
14
23
|
def self.attempt(request)
|
15
24
|
new(request).renderable!&.attempt
|
@@ -25,12 +34,25 @@ module Proscenium
|
|
25
34
|
|
26
35
|
private
|
27
36
|
|
37
|
+
def real_path
|
38
|
+
@request.path
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [String] the path to the file without the leading slash which will be built.
|
42
|
+
def path_to_build
|
43
|
+
@request.path[1..]
|
44
|
+
end
|
45
|
+
|
46
|
+
def sourcemap?
|
47
|
+
@request.path.ends_with?('.map')
|
48
|
+
end
|
49
|
+
|
28
50
|
def renderable?
|
29
51
|
file_readable?
|
30
52
|
end
|
31
53
|
|
32
|
-
def file_readable?
|
33
|
-
return unless (path = clean_path(
|
54
|
+
def file_readable?
|
55
|
+
return unless (path = clean_path(sourcemap? ? real_path[0...-4] : real_path))
|
34
56
|
|
35
57
|
file_stat = File.stat(Pathname(root).join(path.delete_prefix('/').b).to_s)
|
36
58
|
rescue SystemCallError
|
@@ -50,7 +72,7 @@ module Proscenium
|
|
50
72
|
|
51
73
|
def content_type
|
52
74
|
@content_type ||
|
53
|
-
::Rack::Mime.mime_type(::File.extname(
|
75
|
+
::Rack::Mime.mime_type(::File.extname(path_to_build), nil) ||
|
54
76
|
'application/javascript'
|
55
77
|
end
|
56
78
|
|
@@ -59,9 +81,10 @@ module Proscenium
|
|
59
81
|
response.write content
|
60
82
|
response.content_type = content_type
|
61
83
|
response['X-Proscenium-Middleware'] = name
|
84
|
+
response.set_header 'SourceMap', "#{@request.path_info}.map"
|
62
85
|
|
63
86
|
if Proscenium.config.cache_query_string && Proscenium.config.cache_max_age
|
64
|
-
response
|
87
|
+
response.cache! Proscenium.config.cache_max_age
|
65
88
|
end
|
66
89
|
|
67
90
|
yield response if block_given?
|
@@ -69,37 +92,6 @@ module Proscenium
|
|
69
92
|
response.finish
|
70
93
|
end
|
71
94
|
|
72
|
-
def build(cmd)
|
73
|
-
stdout, stderr, status = Open3.capture3(cmd)
|
74
|
-
|
75
|
-
unless status.success?
|
76
|
-
raise self.class::CompileError, stderr if status.exitstatus == 2
|
77
|
-
|
78
|
-
raise BuildError, stderr
|
79
|
-
end
|
80
|
-
|
81
|
-
unless stderr.empty?
|
82
|
-
raise BuildError, "Proscenium build of #{name}:'#{@request.fullpath}' failed -- #{stderr}"
|
83
|
-
end
|
84
|
-
|
85
|
-
stdout
|
86
|
-
end
|
87
|
-
|
88
|
-
def benchmark(type)
|
89
|
-
super logging_message(type)
|
90
|
-
end
|
91
|
-
|
92
|
-
# rubocop:disable Style/FormatStringToken
|
93
|
-
def logging_message(type)
|
94
|
-
format '[Proscenium] Request (%s) %s for %s at %s',
|
95
|
-
type, @request.fullpath, @request.ip, Time.now.to_default_s
|
96
|
-
end
|
97
|
-
# rubocop:enable Style/FormatStringToken
|
98
|
-
|
99
|
-
def logger
|
100
|
-
Rails.logger
|
101
|
-
end
|
102
|
-
|
103
95
|
def name
|
104
96
|
@name ||= self.class.name.split('::').last.downcase
|
105
97
|
end
|
@@ -3,55 +3,29 @@
|
|
3
3
|
module Proscenium
|
4
4
|
class Middleware
|
5
5
|
class Esbuild < Base
|
6
|
-
class CompileError <
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
6
|
+
class CompileError < Base::CompileError
|
7
|
+
def initialize(args)
|
8
|
+
detail = args[:detail]
|
9
|
+
detail = ActiveSupport::HashWithIndifferentAccess.new(Oj.load(detail, mode: :strict))
|
10
|
+
|
11
|
+
args[:detail] = if detail[:location]
|
12
|
+
"#{detail[:text]} in #{detail[:location][:file]}:" +
|
13
|
+
detail[:location][:line].to_s
|
14
|
+
else
|
15
|
+
detail[:text]
|
16
|
+
end
|
17
|
+
|
18
|
+
super args
|
13
19
|
end
|
14
20
|
end
|
15
21
|
|
16
22
|
def attempt
|
17
|
-
|
18
|
-
render_response build(
|
19
|
-
|
20
|
-
cache_query_string,
|
21
|
-
"--lightningcss-bin #{lightningcss_cli} #{path}"
|
22
|
-
].compact.join(' '))
|
23
|
-
end
|
24
|
-
rescue CompileError => e
|
25
|
-
render_response "export default #{e.detail.to_json}" do |response|
|
26
|
-
response['X-Proscenium-Middleware'] = 'Esbuild::CompileError'
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
private
|
31
|
-
|
32
|
-
def path
|
33
|
-
@request.path[1..]
|
34
|
-
end
|
35
|
-
|
36
|
-
def cli
|
37
|
-
if ENV['PROSCENIUM_TEST']
|
38
|
-
'deno run -q --import-map import_map.json -A lib/proscenium/compilers/esbuild.js'
|
39
|
-
else
|
40
|
-
Gem.bin_path 'proscenium', 'esbuild'
|
23
|
+
ActiveSupport::Notifications.instrument('build.proscenium', identifier: path_to_build) do
|
24
|
+
render_response Proscenium::Esbuild.build(path_to_build, root: root,
|
25
|
+
base_url: @request.base_url)
|
41
26
|
end
|
42
|
-
|
43
|
-
|
44
|
-
def lightningcss_cli
|
45
|
-
if ENV['PROSCENIUM_TEST']
|
46
|
-
'bin/lightningcss'
|
47
|
-
else
|
48
|
-
Gem.bin_path 'proscenium', 'lightningcss'
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
def cache_query_string
|
53
|
-
q = Proscenium.config.cache_query_string
|
54
|
-
q ? "--cache-query-string #{q}" : nil
|
27
|
+
rescue Proscenium::Esbuild::CompileError => e
|
28
|
+
raise self.class::CompileError, { file: @request.fullpath, detail: e.message }, caller
|
55
29
|
end
|
56
30
|
end
|
57
31
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Proscenium
|
4
4
|
class Middleware
|
5
|
-
# Handles requests
|
5
|
+
# Handles requests for URL encoded URL's.
|
6
6
|
class Url < Esbuild
|
7
7
|
private
|
8
8
|
|
@@ -11,11 +11,6 @@ module Proscenium
|
|
11
11
|
def renderable?
|
12
12
|
true
|
13
13
|
end
|
14
|
-
|
15
|
-
# @override [Esbuild]
|
16
|
-
def path
|
17
|
-
CGI.unescape(@request.path)[1..]
|
18
|
-
end
|
19
14
|
end
|
20
15
|
end
|
21
16
|
end
|
@@ -9,9 +9,7 @@ module Proscenium
|
|
9
9
|
|
10
10
|
autoload :Base
|
11
11
|
autoload :Esbuild
|
12
|
-
autoload :Runtime
|
13
12
|
autoload :Url
|
14
|
-
autoload :OutsideRoot
|
15
13
|
|
16
14
|
def initialize(app)
|
17
15
|
@app = app
|
@@ -27,37 +25,35 @@ module Proscenium
|
|
27
25
|
|
28
26
|
private
|
29
27
|
|
30
|
-
# Look for the precompiled file in public/assets first, then fallback to the Proscenium
|
31
|
-
# middleware that matches the type of file requested, ie: .js => esbuild.
|
32
|
-
# See Rails.application.config.proscenium.glob_types.
|
33
28
|
def attempt(request)
|
34
29
|
return unless (type = find_type(request))
|
35
30
|
|
36
|
-
file_handler.attempt(request.env) || type.attempt(request)
|
31
|
+
# file_handler.attempt(request.env) || type.attempt(request)
|
32
|
+
|
33
|
+
type.attempt(request)
|
37
34
|
end
|
38
35
|
|
39
|
-
# Returns the type of file being requested using
|
36
|
+
# Returns the type of file being requested using Proscenium::MIDDLEWARE_GLOB_TYPES.
|
40
37
|
def find_type(request)
|
41
38
|
path = Pathname.new(request.path)
|
42
39
|
|
43
|
-
# Non-production only!
|
44
|
-
if request.query_string == 'outsideRoot'
|
45
|
-
return if Rails.env.production?
|
46
|
-
return OutsideRoot if path.fnmatch?(glob_types[:outsideRoot], File::FNM_EXTGLOB)
|
47
|
-
end
|
48
|
-
|
49
40
|
return Url if request.path.match?(glob_types[:url])
|
50
|
-
return
|
51
|
-
return Esbuild if path.fnmatch?(glob_types[:esbuild], File::FNM_EXTGLOB)
|
41
|
+
return Esbuild if path.fnmatch?(application_glob_type, File::FNM_EXTGLOB)
|
52
42
|
end
|
53
43
|
|
44
|
+
# TODO: handle precompiled assets
|
54
45
|
def file_handler
|
55
46
|
::ActionDispatch::FileHandler.new Rails.public_path.join('assets').to_s,
|
56
47
|
headers: { 'X-Proscenium-Middleware' => 'precompiled' }
|
57
48
|
end
|
58
49
|
|
59
50
|
def glob_types
|
60
|
-
|
51
|
+
@glob_types ||= Proscenium::MIDDLEWARE_GLOB_TYPES
|
52
|
+
end
|
53
|
+
|
54
|
+
def application_glob_type
|
55
|
+
paths = Rails.application.config.proscenium.include_paths.join(',')
|
56
|
+
"/{#{paths}}#{glob_types[:application]}"
|
61
57
|
end
|
62
58
|
end
|
63
59
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Proscenium::Phlex::ComponentConcerns
|
4
|
+
module CssModules
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
include Proscenium::CssModule
|
7
|
+
include Proscenium::Phlex::ResolveCssModules
|
8
|
+
|
9
|
+
# class_methods do
|
10
|
+
# # FIXME: Still needed?
|
11
|
+
# def path
|
12
|
+
# pp name, super
|
13
|
+
# pp Module.const_source_location(name).first
|
14
|
+
|
15
|
+
# name && Pathname.new(Module.const_source_location(name).first)
|
16
|
+
# rescue NameError
|
17
|
+
# nil
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def path
|
24
|
+
self.class.path
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'phlex/rails'
|
4
|
+
|
5
|
+
# Include this in your view for additional logic for rendering a full HTML page, usually from a
|
6
|
+
# controller.
|
7
|
+
module Proscenium::Phlex::Page
|
8
|
+
include Phlex::Rails::Helpers::CSPMetaTag
|
9
|
+
include Phlex::Rails::Helpers::CSRFMetaTags
|
10
|
+
include Phlex::Rails::Helpers::FaviconLinkTag
|
11
|
+
include Phlex::Rails::Helpers::PreloadLinkTag
|
12
|
+
include Phlex::Rails::Helpers::StyleSheetLinkTag
|
13
|
+
include Phlex::Rails::Helpers::ActionCableMetaTag
|
14
|
+
include Phlex::Rails::Helpers::AutoDiscoveryLinkTag
|
15
|
+
include Phlex::Rails::Helpers::JavaScriptIncludeTag
|
16
|
+
include Phlex::Rails::Helpers::JavaScriptImportMapTags
|
17
|
+
include Phlex::Rails::Helpers::JavaScriptImportModuleTag
|
18
|
+
|
19
|
+
def self.included(klass)
|
20
|
+
klass.extend(Phlex::Rails::Layout::Interface)
|
21
|
+
end
|
22
|
+
|
23
|
+
def template(&block)
|
24
|
+
doctype
|
25
|
+
html do
|
26
|
+
head
|
27
|
+
body(&block)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def after_template
|
34
|
+
super
|
35
|
+
@_buffer.gsub!('<!-- [SIDE_LOAD_STYLESHEETS] -->', capture { side_load_stylesheets })
|
36
|
+
end
|
37
|
+
|
38
|
+
def page_title
|
39
|
+
Rails.application.class.name.deconstantize
|
40
|
+
end
|
41
|
+
|
42
|
+
def head
|
43
|
+
super do
|
44
|
+
title { page_title }
|
45
|
+
|
46
|
+
yield if block_given?
|
47
|
+
|
48
|
+
csp_meta_tag
|
49
|
+
csrf_meta_tags
|
50
|
+
|
51
|
+
comment { '[SIDE_LOAD_STYLESHEETS]' }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def body
|
56
|
+
super do
|
57
|
+
yield if block_given?
|
58
|
+
|
59
|
+
side_load_javascripts defer: true, type: :module
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -1,10 +1,32 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
#
|
4
|
-
# Renders a div for use with component-manager.
|
4
|
+
# Renders a div for use with @proscenium/component-manager.
|
5
5
|
#
|
6
|
-
|
7
|
-
|
6
|
+
# You can pass props to the component in the `:props` keyword argument.
|
7
|
+
#
|
8
|
+
# By default, the component is lazy loaded when intersecting using IntersectionObserver. Pass in
|
9
|
+
# :lazy as false to disable this and render the component immediately.
|
10
|
+
#
|
11
|
+
# React components are not side loaded at all.
|
12
|
+
#
|
13
|
+
class Proscenium::Phlex::ReactComponent < Phlex::HTML
|
14
|
+
class << self
|
15
|
+
attr_accessor :path, :abstract_class
|
16
|
+
|
17
|
+
def inherited(child)
|
18
|
+
position = caller_locations(1, 1).first.label == 'inherited' ? 2 : 1
|
19
|
+
child.path = Pathname.new caller_locations(position, 1).first.path.sub(/\.rb$/, '')
|
20
|
+
|
21
|
+
super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
self.abstract_class = true
|
26
|
+
|
27
|
+
include Proscenium::Phlex::ComponentConcerns::CssModules
|
28
|
+
|
29
|
+
attr_writer :props, :lazy
|
8
30
|
|
9
31
|
# @param props: [Hash]
|
10
32
|
# @param lazy: [Boolean] Lazy load the component using IntersectionObserver. Default: true.
|
@@ -16,10 +38,32 @@ class Proscenium::Phlex::ReactComponent < Proscenium::Phlex::Component
|
|
16
38
|
# @yield the given block to a `div` within the top level component div. If not given,
|
17
39
|
# `<div>loading...</div>` will be rendered. Use this to display a loading UI while the component
|
18
40
|
# is loading and rendered.
|
19
|
-
def template(&block)
|
20
|
-
div
|
21
|
-
|
22
|
-
|
23
|
-
|
41
|
+
def template(**attributes, &block)
|
42
|
+
component_root(:div, **attributes, &block)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def component_root(element, **attributes, &block)
|
48
|
+
send element, data: { proscenium_component: component_data }, **attributes, &block
|
49
|
+
end
|
50
|
+
|
51
|
+
def props
|
52
|
+
@props ||= {}
|
53
|
+
end
|
54
|
+
|
55
|
+
def lazy
|
56
|
+
instance_variable_defined?(:@lazy) ? @lazy : (@lazy = false)
|
57
|
+
end
|
58
|
+
|
59
|
+
def component_data
|
60
|
+
{
|
61
|
+
path: virtual_path, lazy: lazy,
|
62
|
+
props: props.deep_transform_keys { |k| k.to_s.camelize :lower }
|
63
|
+
}.to_json
|
64
|
+
end
|
65
|
+
|
66
|
+
def virtual_path
|
67
|
+
path.to_s.delete_prefix(Rails.root.to_s)
|
24
68
|
end
|
25
69
|
end
|