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
@@ -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
|