proscenium 0.7.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 +7 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/LICENSE.txt +21 -0
- data/README.md +336 -0
- 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 +43 -0
- data/lib/proscenium/current.rb +9 -0
- data/lib/proscenium/esbuild/golib.rb +97 -0
- data/lib/proscenium/esbuild.rb +32 -0
- data/lib/proscenium/ext/proscenium +0 -0
- data/lib/proscenium/ext/proscenium.h +109 -0
- data/lib/proscenium/helper.rb +19 -0
- data/lib/proscenium/log_subscriber.rb +26 -0
- data/lib/proscenium/middleware/base.rb +100 -0
- data/lib/proscenium/middleware/esbuild.rb +32 -0
- data/lib/proscenium/middleware/url.rb +16 -0
- data/lib/proscenium/middleware.rb +59 -0
- data/lib/proscenium/phlex/component_concerns.rb +27 -0
- data/lib/proscenium/phlex/page.rb +62 -0
- data/lib/proscenium/phlex/react_component.rb +69 -0
- data/lib/proscenium/phlex/resolve_css_modules.rb +67 -0
- data/lib/proscenium/phlex.rb +61 -0
- data/lib/proscenium/railtie.rb +85 -0
- 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 +78 -0
- data/lib/proscenium/version.rb +5 -0
- data/lib/proscenium/view_component/react_component.rb +35 -0
- data/lib/proscenium/view_component/tag_builder.rb +23 -0
- data/lib/proscenium/view_component.rb +55 -0
- data/lib/proscenium.rb +96 -0
- metadata +189 -0
@@ -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('../ext/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
|
Binary file
|
@@ -0,0 +1,109 @@
|
|
1
|
+
/* Code generated by cmd/cgo; DO NOT EDIT. */
|
2
|
+
|
3
|
+
/* package joelmoss/proscenium */
|
4
|
+
|
5
|
+
|
6
|
+
#line 1 "cgo-builtin-export-prolog"
|
7
|
+
|
8
|
+
#include <stddef.h>
|
9
|
+
|
10
|
+
#ifndef GO_CGO_EXPORT_PROLOGUE_H
|
11
|
+
#define GO_CGO_EXPORT_PROLOGUE_H
|
12
|
+
|
13
|
+
#ifndef GO_CGO_GOSTRING_TYPEDEF
|
14
|
+
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
|
15
|
+
#endif
|
16
|
+
|
17
|
+
#endif
|
18
|
+
|
19
|
+
/* Start of preamble from import "C" comments. */
|
20
|
+
|
21
|
+
|
22
|
+
#line 3 "main.go"
|
23
|
+
|
24
|
+
struct Result {
|
25
|
+
int success;
|
26
|
+
char* response;
|
27
|
+
};
|
28
|
+
|
29
|
+
#line 1 "cgo-generated-wrapper"
|
30
|
+
|
31
|
+
|
32
|
+
/* End of preamble from import "C" comments. */
|
33
|
+
|
34
|
+
|
35
|
+
/* Start of boilerplate cgo prologue. */
|
36
|
+
#line 1 "cgo-gcc-export-header-prolog"
|
37
|
+
|
38
|
+
#ifndef GO_CGO_PROLOGUE_H
|
39
|
+
#define GO_CGO_PROLOGUE_H
|
40
|
+
|
41
|
+
typedef signed char GoInt8;
|
42
|
+
typedef unsigned char GoUint8;
|
43
|
+
typedef short GoInt16;
|
44
|
+
typedef unsigned short GoUint16;
|
45
|
+
typedef int GoInt32;
|
46
|
+
typedef unsigned int GoUint32;
|
47
|
+
typedef long long GoInt64;
|
48
|
+
typedef unsigned long long GoUint64;
|
49
|
+
typedef GoInt64 GoInt;
|
50
|
+
typedef GoUint64 GoUint;
|
51
|
+
typedef size_t GoUintptr;
|
52
|
+
typedef float GoFloat32;
|
53
|
+
typedef double GoFloat64;
|
54
|
+
#ifdef _MSC_VER
|
55
|
+
#include <complex.h>
|
56
|
+
typedef _Fcomplex GoComplex64;
|
57
|
+
typedef _Dcomplex GoComplex128;
|
58
|
+
#else
|
59
|
+
typedef float _Complex GoComplex64;
|
60
|
+
typedef double _Complex GoComplex128;
|
61
|
+
#endif
|
62
|
+
|
63
|
+
/*
|
64
|
+
static assertion to make sure the file is being used on architecture
|
65
|
+
at least with matching size of GoInt.
|
66
|
+
*/
|
67
|
+
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
|
68
|
+
|
69
|
+
#ifndef GO_CGO_GOSTRING_TYPEDEF
|
70
|
+
typedef _GoString_ GoString;
|
71
|
+
#endif
|
72
|
+
typedef void *GoMap;
|
73
|
+
typedef void *GoChan;
|
74
|
+
typedef struct { void *t; void *v; } GoInterface;
|
75
|
+
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
|
76
|
+
|
77
|
+
#endif
|
78
|
+
|
79
|
+
/* End of boilerplate cgo prologue. */
|
80
|
+
|
81
|
+
#ifdef __cplusplus
|
82
|
+
extern "C" {
|
83
|
+
#endif
|
84
|
+
|
85
|
+
|
86
|
+
// Build the given `path` in the `root`.
|
87
|
+
//
|
88
|
+
// - path - The path to build relative to `root`.
|
89
|
+
// - root - The working directory.
|
90
|
+
// - baseUrl - base URL of the Rails app. eg. https://example.com
|
91
|
+
// - env - The environment (1 = development, 2 = test, 3 = production)
|
92
|
+
// - importMap - Path to the import map relative to `root`.
|
93
|
+
// - bundle
|
94
|
+
// - debug
|
95
|
+
//
|
96
|
+
extern struct Result build(char* filepath, char* root, char* baseUrl, unsigned int env, char* importMap, GoUint8 bundle, GoUint8 debug);
|
97
|
+
|
98
|
+
// Resolve the given `path` relative to the `root`.
|
99
|
+
//
|
100
|
+
// - path - The path to build relative to `root`.
|
101
|
+
// - root - The working directory.
|
102
|
+
// - env - The environment (1 = development, 2 = test, 3 = production)
|
103
|
+
// - importMap - Path to the import map relative to `root`.
|
104
|
+
//
|
105
|
+
extern struct Result resolve(char* path, char* root, unsigned int env, char* importMap);
|
106
|
+
|
107
|
+
#ifdef __cplusplus
|
108
|
+
}
|
109
|
+
#endif
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Proscenium
|
4
|
+
module Helper
|
5
|
+
def compute_asset_path(path, options = {})
|
6
|
+
if %i[javascript stylesheet].include?(options[:type])
|
7
|
+
result = "/#{path}"
|
8
|
+
|
9
|
+
if (qs = Proscenium.config.cache_query_string)
|
10
|
+
result << "?#{qs}"
|
11
|
+
end
|
12
|
+
|
13
|
+
return result
|
14
|
+
end
|
15
|
+
|
16
|
+
super
|
17
|
+
end
|
18
|
+
end
|
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
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'open3'
|
4
|
+
require 'oj'
|
5
|
+
|
6
|
+
module Proscenium
|
7
|
+
class Middleware
|
8
|
+
class Base
|
9
|
+
include ActiveSupport::Benchmarkable
|
10
|
+
|
11
|
+
# Error when the result of the build returns an error. For example, when esbuild returns
|
12
|
+
# errors.
|
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
|
22
|
+
|
23
|
+
def self.attempt(request)
|
24
|
+
new(request).renderable!&.attempt
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(request)
|
28
|
+
@request = request
|
29
|
+
end
|
30
|
+
|
31
|
+
def renderable!
|
32
|
+
renderable? ? self : nil
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
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
|
+
|
50
|
+
def renderable?
|
51
|
+
file_readable?
|
52
|
+
end
|
53
|
+
|
54
|
+
def file_readable?
|
55
|
+
return unless (path = clean_path(sourcemap? ? real_path[0...-4] : real_path))
|
56
|
+
|
57
|
+
file_stat = File.stat(Pathname(root).join(path.delete_prefix('/').b).to_s)
|
58
|
+
rescue SystemCallError
|
59
|
+
false
|
60
|
+
else
|
61
|
+
file_stat.file? && file_stat.readable?
|
62
|
+
end
|
63
|
+
|
64
|
+
def clean_path(file)
|
65
|
+
path = Rack::Utils.unescape_path file.chomp('/').delete_prefix('/')
|
66
|
+
Rack::Utils.clean_path_info path if Rack::Utils.valid_path? path
|
67
|
+
end
|
68
|
+
|
69
|
+
def root
|
70
|
+
@root ||= Rails.root.to_s
|
71
|
+
end
|
72
|
+
|
73
|
+
def content_type
|
74
|
+
@content_type ||
|
75
|
+
::Rack::Mime.mime_type(::File.extname(path_to_build), nil) ||
|
76
|
+
'application/javascript'
|
77
|
+
end
|
78
|
+
|
79
|
+
def render_response(content)
|
80
|
+
response = Rack::Response.new
|
81
|
+
response.write content
|
82
|
+
response.content_type = content_type
|
83
|
+
response['X-Proscenium-Middleware'] = name
|
84
|
+
response.set_header 'SourceMap', "#{@request.path_info}.map"
|
85
|
+
|
86
|
+
if Proscenium.config.cache_query_string && Proscenium.config.cache_max_age
|
87
|
+
response.cache! Proscenium.config.cache_max_age
|
88
|
+
end
|
89
|
+
|
90
|
+
yield response if block_given?
|
91
|
+
|
92
|
+
response.finish
|
93
|
+
end
|
94
|
+
|
95
|
+
def name
|
96
|
+
@name ||= self.class.name.split('::').last.downcase
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Proscenium
|
4
|
+
class Middleware
|
5
|
+
class Esbuild < Base
|
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
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def attempt
|
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)
|
26
|
+
end
|
27
|
+
rescue Proscenium::Esbuild::CompileError => e
|
28
|
+
raise self.class::CompileError, { file: @request.fullpath, detail: e.message }, caller
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Proscenium
|
4
|
+
class Middleware
|
5
|
+
# Handles requests for URL encoded URL's.
|
6
|
+
class Url < Esbuild
|
7
|
+
private
|
8
|
+
|
9
|
+
# @override [Esbuild] It's a URL, so always assume it is renderable (we won't actually know
|
10
|
+
# until it's downloaded).
|
11
|
+
def renderable?
|
12
|
+
true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Proscenium
|
4
|
+
class Middleware
|
5
|
+
extend ActiveSupport::Autoload
|
6
|
+
|
7
|
+
# Error when the build command fails.
|
8
|
+
class BuildError < StandardError; end
|
9
|
+
|
10
|
+
autoload :Base
|
11
|
+
autoload :Esbuild
|
12
|
+
autoload :Url
|
13
|
+
|
14
|
+
def initialize(app)
|
15
|
+
@app = app
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(env)
|
19
|
+
request = Rack::Request.new(env)
|
20
|
+
|
21
|
+
return @app.call(env) if !request.get? && !request.head?
|
22
|
+
|
23
|
+
attempt(request) || @app.call(env)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def attempt(request)
|
29
|
+
return unless (type = find_type(request))
|
30
|
+
|
31
|
+
# file_handler.attempt(request.env) || type.attempt(request)
|
32
|
+
|
33
|
+
type.attempt(request)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns the type of file being requested using Proscenium::MIDDLEWARE_GLOB_TYPES.
|
37
|
+
def find_type(request)
|
38
|
+
path = Pathname.new(request.path)
|
39
|
+
|
40
|
+
return Url if request.path.match?(glob_types[:url])
|
41
|
+
return Esbuild if path.fnmatch?(application_glob_type, File::FNM_EXTGLOB)
|
42
|
+
end
|
43
|
+
|
44
|
+
# TODO: handle precompiled assets
|
45
|
+
def file_handler
|
46
|
+
::ActionDispatch::FileHandler.new Rails.public_path.join('assets').to_s,
|
47
|
+
headers: { 'X-Proscenium-Middleware' => 'precompiled' }
|
48
|
+
end
|
49
|
+
|
50
|
+
def glob_types
|
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]}"
|
57
|
+
end
|
58
|
+
end
|
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
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Renders a div for use with @proscenium/component-manager.
|
5
|
+
#
|
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
|
30
|
+
|
31
|
+
# @param props: [Hash]
|
32
|
+
# @param lazy: [Boolean] Lazy load the component using IntersectionObserver. Default: true.
|
33
|
+
def initialize(props: {}, lazy: true) # rubocop:disable Lint/MissingSuper
|
34
|
+
@props = props
|
35
|
+
@lazy = lazy
|
36
|
+
end
|
37
|
+
|
38
|
+
# @yield the given block to a `div` within the top level component div. If not given,
|
39
|
+
# `<div>loading...</div>` will be rendered. Use this to display a loading UI while the component
|
40
|
+
# is loading and rendered.
|
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)
|
68
|
+
end
|
69
|
+
end
|