proscenium 0.5.1-x86_64-linux → 0.7.0-x86_64-linux

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +128 -92
  3. data/lib/proscenium/css_module/class_names_resolver.rb +66 -0
  4. data/lib/proscenium/css_module/resolver.rb +76 -0
  5. data/lib/proscenium/css_module.rb +18 -39
  6. data/lib/proscenium/esbuild/golib.rb +97 -0
  7. data/lib/proscenium/esbuild.rb +32 -0
  8. data/lib/proscenium/ext/proscenium +0 -0
  9. data/lib/proscenium/ext/proscenium.h +109 -0
  10. data/lib/proscenium/helper.rb +0 -23
  11. data/lib/proscenium/log_subscriber.rb +26 -0
  12. data/lib/proscenium/middleware/base.rb +28 -36
  13. data/lib/proscenium/middleware/esbuild.rb +18 -44
  14. data/lib/proscenium/middleware/url.rb +1 -6
  15. data/lib/proscenium/middleware.rb +12 -16
  16. data/lib/proscenium/phlex/component_concerns.rb +27 -0
  17. data/lib/proscenium/phlex/page.rb +62 -0
  18. data/lib/proscenium/phlex/react_component.rb +52 -8
  19. data/lib/proscenium/phlex/resolve_css_modules.rb +67 -0
  20. data/lib/proscenium/phlex.rb +34 -33
  21. data/lib/proscenium/railtie.rb +41 -67
  22. data/lib/proscenium/side_load/ensure_loaded.rb +25 -0
  23. data/lib/proscenium/side_load/helper.rb +25 -0
  24. data/lib/proscenium/side_load/monkey.rb +48 -0
  25. data/lib/proscenium/side_load.rb +58 -52
  26. data/lib/proscenium/version.rb +1 -1
  27. data/lib/proscenium/view_component/react_component.rb +14 -0
  28. data/lib/proscenium/view_component.rb +28 -18
  29. data/lib/proscenium.rb +79 -2
  30. metadata +35 -74
  31. data/app/channels/proscenium/connection.rb +0 -13
  32. data/app/channels/proscenium/reload_channel.rb +0 -9
  33. data/bin/esbuild +0 -0
  34. data/bin/lightningcss +0 -0
  35. data/config/routes.rb +0 -7
  36. data/lib/proscenium/compiler.js +0 -84
  37. data/lib/proscenium/compilers/esbuild/argument_error.js +0 -24
  38. data/lib/proscenium/compilers/esbuild/compile_error.js +0 -148
  39. data/lib/proscenium/compilers/esbuild/css/postcss.js +0 -67
  40. data/lib/proscenium/compilers/esbuild/css_plugin.js +0 -172
  41. data/lib/proscenium/compilers/esbuild/env_plugin.js +0 -46
  42. data/lib/proscenium/compilers/esbuild/http_bundle_plugin.js +0 -53
  43. data/lib/proscenium/compilers/esbuild/import_map.js +0 -59
  44. data/lib/proscenium/compilers/esbuild/resolve_plugin.js +0 -205
  45. data/lib/proscenium/compilers/esbuild/setup_plugin.js +0 -45
  46. data/lib/proscenium/compilers/esbuild/solidjs_plugin.js +0 -24
  47. data/lib/proscenium/compilers/esbuild.bench.js +0 -14
  48. data/lib/proscenium/compilers/esbuild.js +0 -179
  49. data/lib/proscenium/link_to_helper.rb +0 -40
  50. data/lib/proscenium/middleware/lightningcss.rb +0 -64
  51. data/lib/proscenium/middleware/outside_root.rb +0 -26
  52. data/lib/proscenium/middleware/runtime.rb +0 -22
  53. data/lib/proscenium/middleware/static.rb +0 -14
  54. data/lib/proscenium/phlex/component.rb +0 -9
  55. data/lib/proscenium/precompile.rb +0 -31
  56. data/lib/proscenium/runtime/auto_reload.js +0 -40
  57. data/lib/proscenium/runtime/react_shim/index.js +0 -1
  58. data/lib/proscenium/runtime/react_shim/package.json +0 -5
  59. data/lib/proscenium/utils.js +0 -8
  60. data/lib/tasks/assets.rake +0 -19
@@ -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
@@ -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; end
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?(file = @request.path_info)
33
- return unless (path = clean_path(file))
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(@request.path_info), nil) ||
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['Cache-Control'] = "public, max-age=#{Proscenium.config.cache_max_age}"
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 < StandardError
7
- attr_reader :detail
8
-
9
- def initialize(detail)
10
- @detail = ActiveSupport::HashWithIndifferentAccess.new(Oj.load(detail, mode: :strict))
11
-
12
- super "#{@detail[:text]} in #{@detail[:location][:file]}:#{@detail[:location][:line]}"
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
- benchmark :esbuild do
18
- render_response build([
19
- "#{cli} --root #{root}",
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
- end
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 prefixed with "url:https://"; downloading, caching, and compiling them.
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 Rails.application.config.proscenium.glob_types.
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 Runtime if path.fnmatch?(glob_types[:runtime], File::FNM_EXTGLOB)
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
- Rails.application.config.proscenium.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]}"
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