proscenium 0.9.1-x86_64-darwin → 0.11.0.pre.1-x86_64-darwin

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +423 -63
  3. data/lib/proscenium/builder.rb +126 -0
  4. data/lib/proscenium/css_module/path.rb +31 -0
  5. data/lib/proscenium/css_module/transformer.rb +76 -0
  6. data/lib/proscenium/css_module.rb +6 -28
  7. data/lib/proscenium/ensure_loaded.rb +27 -0
  8. data/lib/proscenium/ext/proscenium +0 -0
  9. data/lib/proscenium/ext/proscenium.h +19 -12
  10. data/lib/proscenium/helper.rb +62 -0
  11. data/lib/proscenium/importer.rb +110 -0
  12. data/lib/proscenium/libs/react-manager/index.jsx +88 -0
  13. data/lib/proscenium/libs/react-manager/react.js +2 -0
  14. data/lib/proscenium/libs/stimulus-loading.js +83 -0
  15. data/lib/proscenium/log_subscriber.rb +1 -2
  16. data/lib/proscenium/middleware/base.rb +1 -1
  17. data/lib/proscenium/middleware/esbuild.rb +3 -5
  18. data/lib/proscenium/middleware.rb +7 -1
  19. data/lib/proscenium/{side_load/monkey.rb → monkey.rb} +16 -12
  20. data/lib/proscenium/phlex/{resolve_css_modules.rb → css_modules.rb} +6 -20
  21. data/lib/proscenium/phlex/page.rb +2 -2
  22. data/lib/proscenium/phlex/react_component.rb +27 -64
  23. data/lib/proscenium/phlex.rb +10 -29
  24. data/lib/proscenium/railtie.rb +20 -22
  25. data/lib/proscenium/react_componentable.rb +94 -0
  26. data/lib/proscenium/resolver.rb +37 -0
  27. data/lib/proscenium/side_load.rb +13 -72
  28. data/lib/proscenium/source_path.rb +15 -0
  29. data/lib/proscenium/utils.rb +13 -0
  30. data/lib/proscenium/version.rb +1 -1
  31. data/lib/proscenium/view_component/css_modules.rb +11 -0
  32. data/lib/proscenium/view_component/react_component.rb +15 -28
  33. data/lib/proscenium/view_component/sideload.rb +4 -0
  34. data/lib/proscenium/view_component.rb +8 -31
  35. data/lib/proscenium.rb +24 -68
  36. metadata +21 -58
  37. data/lib/proscenium/css_module/class_names_resolver.rb +0 -66
  38. data/lib/proscenium/css_module/resolver.rb +0 -76
  39. data/lib/proscenium/current.rb +0 -9
  40. data/lib/proscenium/esbuild/golib.rb +0 -97
  41. data/lib/proscenium/esbuild.rb +0 -32
  42. data/lib/proscenium/phlex/component_concerns.rb +0 -27
  43. data/lib/proscenium/side_load/ensure_loaded.rb +0 -25
  44. data/lib/proscenium/side_load/helper.rb +0 -25
  45. data/lib/proscenium/view_component/tag_builder.rb +0 -23
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ffi'
4
+ require 'oj'
5
+
6
+ module Proscenium
7
+ class Builder
8
+ class CompileError < StandardError; end
9
+
10
+ class Result < FFI::Struct
11
+ layout :success, :bool,
12
+ :response, :string
13
+ end
14
+
15
+ module Request
16
+ extend FFI::Library
17
+ ffi_lib Pathname.new(__dir__).join('ext/proscenium').to_s
18
+
19
+ enum :environment, [:development, 1, :test, :production]
20
+
21
+ attach_function :build, [
22
+ :string, # path or entry point. multiple can be given by separating with a semi-colon
23
+ :string, # base URL of the Rails app. eg. https://example.com
24
+ :string, # path to import map, relative to root
25
+ :string, # ENV variables as a JSON string
26
+
27
+ # Config
28
+ :string, # root
29
+ :environment, # Rails environment as a Symbol
30
+ :bool, # code splitting enabled?
31
+ :bool # debugging enabled?
32
+ ], Result.by_value
33
+
34
+ attach_function :resolve, [
35
+ :string, # path or entry point
36
+ :string, # path to import map, relative to root
37
+
38
+ # Config
39
+ :string, # root
40
+ :environment # Rails environment as a Symbol
41
+ ], Result.by_value
42
+ end
43
+
44
+ class BuildError < StandardError
45
+ attr_reader :error, :path
46
+
47
+ def initialize(path, error)
48
+ error = Oj.load(error, mode: :strict).deep_transform_keys(&:underscore)
49
+
50
+ super "Failed to build '#{path}' -- #{error['text']}"
51
+ end
52
+ end
53
+
54
+ class ResolveError < StandardError
55
+ attr_reader :error_msg, :path
56
+
57
+ def initialize(path, error_msg)
58
+ super "Failed to resolve '#{path}' -- #{error_msg}"
59
+ end
60
+ end
61
+
62
+ def self.build(path, root: nil, base_url: nil)
63
+ new(root: root, base_url: base_url).build(path)
64
+ end
65
+
66
+ def self.resolve(path, root: nil)
67
+ new(root: root).resolve(path)
68
+ end
69
+
70
+ def initialize(root: nil, base_url: nil)
71
+ @root = root || Rails.root
72
+ @base_url = base_url
73
+ end
74
+
75
+ def build(path)
76
+ ActiveSupport::Notifications.instrument('build.proscenium', identifier: path) do
77
+ result = Request.build(path, @base_url, import_map, env_vars.to_json,
78
+ @root.to_s,
79
+ Rails.env.to_sym,
80
+ Proscenium.config.code_splitting,
81
+ Proscenium.config.debug)
82
+
83
+ raise BuildError.new(path, result[:response]) unless result[:success]
84
+
85
+ result[:response]
86
+ end
87
+ end
88
+
89
+ def resolve(path)
90
+ ActiveSupport::Notifications.instrument('resolve.proscenium', identifier: path) do
91
+ result = Request.resolve(path, import_map, @root.to_s, Rails.env.to_sym)
92
+ raise ResolveError.new(path, result[:response]) unless result[:success]
93
+
94
+ result[:response]
95
+ end
96
+ end
97
+
98
+ private
99
+
100
+ # Build the ENV variables as determined by `Proscenium.config.env_vars` and
101
+ # `Proscenium::DEFAULT_ENV_VARS` to pass to esbuild.
102
+ def env_vars
103
+ ENV['NODE_ENV'] = ENV.fetch('RAILS_ENV', nil)
104
+ ENV.slice(*Proscenium.config.env_vars + Proscenium::DEFAULT_ENV_VARS)
105
+ end
106
+
107
+ def cache_query_string
108
+ q = Proscenium.config.cache_query_string
109
+ q ? "--cache-query-string #{q}" : nil
110
+ end
111
+
112
+ def import_map
113
+ return unless (path = Rails.root&.join('config'))
114
+
115
+ if (json = path.join('import_map.json')).exist?
116
+ return json.relative_path_from(@root).to_s
117
+ end
118
+
119
+ if (js = path.join('import_map.js')).exist?
120
+ return js.relative_path_from(@root).to_s
121
+ end
122
+
123
+ nil
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium
4
+ module CssModule::Path
5
+ # Returns the path to the CSS module file for this class, where the file is located alongside
6
+ # the class file, and has the same name as the class file, but with a `.module.css` extension.
7
+ #
8
+ # If the CSS module file does not exist, it's ancestry is checked, returning the first that
9
+ # exists. Then finally `nil` is returned if never found.
10
+ #
11
+ # @return [Pathname]
12
+ def css_module_path
13
+ return @css_module_path if instance_variable_defined?(:@css_module_path)
14
+
15
+ path = source_path.sub_ext('.module.css')
16
+ @css_module_path = path.exist? ? path : nil
17
+
18
+ unless @css_module_path
19
+ klass = superclass
20
+
21
+ while klass.respond_to?(:css_module_path) && !klass.abstract_class
22
+ break if (@css_module_path = klass.css_module_path)
23
+
24
+ klass = klass.superclass
25
+ end
26
+ end
27
+
28
+ @css_module_path
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium
4
+ class CssModule::Transformer
5
+ FILE_EXT = '.module.css'
6
+
7
+ def self.class_names(path, *names)
8
+ new(path).class_names(*names)
9
+ end
10
+
11
+ def initialize(source_path)
12
+ @source_path = source_path
13
+ @source_path = Pathname.new(@source_path) unless @source_path.is_a?(Pathname)
14
+ @source_path = @source_path.sub_ext(FILE_EXT) unless @source_path.to_s.end_with?(FILE_EXT)
15
+ end
16
+
17
+ # Transform each of the given class `names` to their respective CSS module name, which consist
18
+ # of the name, and suffixed with the digest of the resolved source path.
19
+ #
20
+ # Any name beginning with '@' will be transformed to a CSS module name. If `require_prefix` is
21
+ # false, then all names will be transformed to a CSS module name regardless of whether or not
22
+ # they begin with '@'.
23
+ #
24
+ # class_names :@my_module_name, :my_class_name
25
+ #
26
+ # Note that the generated digest is based on the resolved (URL) path, not the original path.
27
+ #
28
+ # You can also provide a path specifier and class name. The path will be the URL path to a
29
+ # stylesheet. The class name will be the name of the class to transform.
30
+ #
31
+ # class_names "/lib/button@default"
32
+ # class_names "mypackage/button@large"
33
+ # class_names "@scoped/package/button@small"
34
+ #
35
+ # @param names [String,Symbol,Array<String,Symbol>]
36
+ # @param require_prefix: [Boolean] whether or not to require the `@` prefix.
37
+ # @return [Array<String>] the transformed CSS module names.
38
+ def class_names(*names, require_prefix: true)
39
+ names.map do |name|
40
+ name = name.to_s if name.is_a?(Symbol)
41
+
42
+ if name.include?('/')
43
+ if name.start_with?('@')
44
+ # Scoped bare specifier (eg. "@scoped/package/lib/button@default").
45
+ _, path, name = name.split('@')
46
+ path = "@#{path}"
47
+ elsif name.start_with?('/')
48
+ # Local path with leading slash.
49
+ path, name = name[1..].split('@')
50
+ else
51
+ # Bare specifier (eg. "mypackage/lib/button@default").
52
+ path, name = name.split('@')
53
+ end
54
+
55
+ class_name! name, path: "#{path}#{FILE_EXT}"
56
+ elsif name.start_with?('@')
57
+ class_name! name[1..]
58
+ else
59
+ require_prefix ? name : class_name!(name)
60
+ end
61
+ end
62
+ end
63
+
64
+ def class_name!(name, path: @source_path)
65
+ resolved_path = Resolver.resolve(path.to_s)
66
+ digest = Importer.import(resolved_path)
67
+
68
+ sname = name.to_s
69
+ if sname.start_with?('_')
70
+ "_#{sname[1..]}-#{digest}"
71
+ else
72
+ "#{sname}-#{digest}"
73
+ end
74
+ end
75
+ end
76
+ end
@@ -3,41 +3,19 @@
3
3
  module Proscenium::CssModule
4
4
  extend ActiveSupport::Autoload
5
5
 
6
- class StylesheetNotFound < StandardError
7
- def initialize(pathname)
8
- @pathname = pathname
9
- super
10
- end
11
-
12
- def message
13
- "Stylesheet is required, but does not exist: #{@pathname}"
14
- end
15
- end
16
-
17
- autoload :ClassNamesResolver
18
- autoload :Resolver # deprecated
19
-
20
- # Like `css_modules`, but will raise if the stylesheet cannot be found.
21
- #
22
- # @param name [Array, String]
23
- def css_module!(names)
24
- cssm.class_names!(names).join ' '
25
- end
6
+ autoload :Path
7
+ autoload :Transformer
26
8
 
27
9
  # Accepts one or more CSS class names, and transforms them into CSS module names.
28
10
  #
29
- # @param name [Array, String]
30
- def css_module(names)
31
- cssm.class_names(names).join ' '
11
+ # @param name [String,Symbol,Array<String,Symbol>]
12
+ def css_module(*names)
13
+ cssm.class_names(*names, require_prefix: false).join ' '
32
14
  end
33
15
 
34
16
  private
35
17
 
36
- def path
37
- self.class.path
38
- end
39
-
40
18
  def cssm
41
- @cssm ||= Resolver.new(path)
19
+ @cssm ||= Transformer.new(self.class.css_module_path)
42
20
  end
43
21
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium
4
+ NotIncludedError = Class.new(StandardError)
5
+
6
+ module EnsureLoaded
7
+ def self.included(child)
8
+ child.class_eval do
9
+ append_after_action do
10
+ if request.format.html? && Importer.imported?
11
+ if Importer.js_imported?
12
+ raise NotIncludedError, 'There are javascripts to be included, but they have ' \
13
+ 'not been included in the page. Did you forget to add the ' \
14
+ '`#include_javascripts` helper in your views?'
15
+ end
16
+
17
+ if Importer.css_imported?
18
+ raise NotIncludedError, 'There are stylesheets to be included, but they have ' \
19
+ 'not been included in the page. Did you forget to add the ' \
20
+ '`#include_stylesheets` helper in your views?'
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
Binary file
@@ -85,23 +85,30 @@ extern "C" {
85
85
 
86
86
  // Build the given `path` in the `root`.
87
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
- // - debug
88
+ // BuildOptions
89
+ // - path - The path to build relative to `root`. Multiple paths can be given by separating them
90
+ // with a semi-colon.
91
+ // - baseUrl - base URL of the Rails app. eg. https://example.com
92
+ // - importMap - Path to the import map relative to `root`.
93
+ // - envVars - JSON string of environment variables.
94
+ // Config:
95
+ // - root - The working directory.
96
+ // - env - The environment (1 = development, 2 = test, 3 = production)
97
+ // - codeSpitting?
98
+ // - debug?
94
99
  //
95
- extern struct Result build(char* filepath, char* root, char* baseUrl, unsigned int env, char* importMap, GoUint8 debug);
100
+ extern struct Result build(char* filepath, char* baseUrl, char* importMap, char* envVars, char* root, unsigned int env, GoUint8 codeSplitting, GoUint8 debug);
96
101
 
97
102
  // Resolve the given `path` relative to the `root`.
98
103
  //
99
- // - path - The path to build relative to `root`.
100
- // - root - The working directory.
101
- // - env - The environment (1 = development, 2 = test, 3 = production)
102
- // - importMap - Path to the import map relative to `root`.
104
+ // ResolveOptions
105
+ // - path - The path to build relative to `root`.
106
+ // - importMap - Path to the import map relative to `root`.
107
+ // Config
108
+ // - root - The working directory.
109
+ // - env - The environment (1 = development, 2 = test, 3 = production)
103
110
  //
104
- extern struct Result resolve(char* path, char* root, unsigned int env, char* importMap);
111
+ extern struct Result resolve(char* path, char* importMap, char* root, unsigned int env);
105
112
 
106
113
  #ifdef __cplusplus
107
114
  }
@@ -15,5 +15,67 @@ module Proscenium
15
15
 
16
16
  super
17
17
  end
18
+
19
+ # Accepts one or more CSS class names, and transforms them into CSS module names.
20
+ #
21
+ # @see CssModule::Transformer#class_names
22
+ # @param name [String,Symbol,Array<String,Symbol>]
23
+ def css_module(*names)
24
+ path = Pathname.new(@lookup_context.find(@virtual_path).identifier).sub_ext('')
25
+ CssModule::Transformer.new(path).class_names(*names, require_prefix: false).join ' '
26
+ end
27
+
28
+ def include_stylesheets(**options)
29
+ out = []
30
+ Importer.each_stylesheet(delete: true) do |path, _path_options|
31
+ out << stylesheet_link_tag(path, extname: false, **options)
32
+ end
33
+ out.join("\n").html_safe
34
+ end
35
+ alias side_load_stylesheets include_stylesheets
36
+ deprecate side_load_stylesheets: 'Use `include_stylesheets` instead', deprecator: Deprecator.new
37
+
38
+ def include_javascripts(**options) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
39
+ out = []
40
+
41
+ if Rails.application.config.proscenium.code_splitting && Importer.multiple_js_imported?
42
+ imports = Importer.imported.dup
43
+
44
+ paths_to_build = []
45
+ Importer.each_javascript(delete: true) do |x, _|
46
+ paths_to_build << x.delete_prefix('/')
47
+ end
48
+
49
+ result = Builder.build(paths_to_build.join(';'), base_url: request.base_url)
50
+
51
+ # Remove the react components from the results, so they are not side loaded. Instead they
52
+ # are lazy loaded by the component manager.
53
+
54
+ scripts = {}
55
+ result.split(';').each do |x|
56
+ inpath, outpath = x.split('::')
57
+ inpath.prepend '/'
58
+ outpath.delete_prefix! 'public'
59
+
60
+ next unless imports.key?(inpath)
61
+
62
+ if (import = imports[inpath]).delete(:lazy)
63
+ scripts[inpath] = import.merge(outpath: outpath)
64
+ else
65
+ out << javascript_include_tag(outpath, extname: false, **options)
66
+ end
67
+ end
68
+
69
+ out << javascript_tag("window.prosceniumLazyScripts = #{scripts.to_json}")
70
+ else
71
+ Importer.each_javascript(delete: true) do |path, _path_options|
72
+ out << javascript_include_tag(path, extname: false, **options)
73
+ end
74
+ end
75
+
76
+ out.join("\n").html_safe
77
+ end
78
+ alias side_load_javascripts include_javascripts
79
+ deprecate side_load_javascripts: 'Use `include_javascripts` instead', deprecator: Deprecator.new
18
80
  end
19
81
  end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/current_attributes'
4
+
5
+ module Proscenium
6
+ class Importer < ActiveSupport::CurrentAttributes
7
+ JS_EXTENSIONS = %w[.tsx .ts .jsx .js].freeze
8
+ CSS_EXTENSIONS = %w[.module.css .css].freeze
9
+
10
+ # Holds the JS and CSS files to include in the current request.
11
+ #
12
+ # Example:
13
+ # {
14
+ # '/path/to/input/file.js': {
15
+ # output: '/path/to/compiled/file.js',
16
+ # **options
17
+ # }
18
+ # }
19
+ attribute :imported
20
+
21
+ class << self
22
+ # Import the given `filepath`. This is idempotent - it will never include duplicates.
23
+ #
24
+ # @param filepath [String] Absolute path (relative to Rails root) of the file to import.
25
+ # Should be the actual asset file, eg. app.css, some/component.js.
26
+ # @param resolve [String] description of the file to resolve and import.
27
+ # @return [String] the digest of the imported file path if a css module (*.module.css).
28
+ def import(filepath = nil, resolve: nil, **options)
29
+ self.imported ||= {}
30
+
31
+ filepath = Resolver.resolve(resolve) if !filepath && resolve
32
+ css_module = filepath.end_with?('.module.css')
33
+
34
+ unless self.imported.key?(filepath)
35
+ # ActiveSupport::Notifications.instrument('sideload.proscenium', identifier: value)
36
+
37
+ self.imported[filepath] = { **options }
38
+ self.imported[filepath][:digest] = Utils.digest(filepath) if css_module
39
+ end
40
+
41
+ css_module ? self.imported[filepath][:digest] : nil
42
+ end
43
+
44
+ # Sideloads JS and CSS assets for the given Ruby filepath.
45
+ #
46
+ # Any files with the same base name and matching a supported extension will be sideloaded.
47
+ # Only one JS and one CSS file will be sideloaded, with the first match used in the following
48
+ # order:
49
+ # - JS extensions: .tsx, .ts, .jsx, and .js.
50
+ # - CSS extensions: .css.module, and .css.
51
+ #
52
+ # Example:
53
+ # - `app/views/layouts/application.rb`
54
+ # - `app/views/layouts/application.css`
55
+ # - `app/views/layouts/application.js`
56
+ # - `app/views/layouts/application.tsx`
57
+ #
58
+ # A request to sideload `app/views/layouts/application.rb` will result in `application.css`
59
+ # and `application.tsx` being sideloaded. `application.js` will not be sideloaded because the
60
+ # `.tsx` extension is matched first.
61
+ #
62
+ # @param filepath [Pathname] Absolute file system path of the Ruby file to sideload.
63
+ def sideload(filepath, **options)
64
+ return unless Proscenium.config.side_load
65
+
66
+ filepath = Rails.root.join(filepath) unless filepath.is_a?(Pathname)
67
+ filepath = filepath.sub_ext('')
68
+
69
+ import_if_exists = lambda do |x|
70
+ if (fp = filepath.sub_ext(x)).exist?
71
+ import(Resolver.resolve(fp.to_s), sideloaded: true, **options)
72
+ end
73
+ end
74
+
75
+ JS_EXTENSIONS.find(&import_if_exists)
76
+ CSS_EXTENSIONS.find(&import_if_exists)
77
+ end
78
+
79
+ def each_stylesheet(delete: false)
80
+ return if imported.blank?
81
+
82
+ blk = proc { |key, options| key.end_with?(*CSS_EXTENSIONS) && yield(key, options) }
83
+ delete ? imported.delete_if(&blk) : imported.each(&blk)
84
+ end
85
+
86
+ def each_javascript(delete: false)
87
+ return if imported.blank?
88
+
89
+ blk = proc { |key, options| key.end_with?(*JS_EXTENSIONS) && yield(key, options) }
90
+ delete ? imported.delete_if(&blk) : imported.each(&blk)
91
+ end
92
+
93
+ def css_imported?
94
+ imported&.keys&.any? { |x| x.end_with?(*CSS_EXTENSIONS) }
95
+ end
96
+
97
+ def js_imported?
98
+ imported&.keys&.any? { |x| x.end_with?(*JS_EXTENSIONS) }
99
+ end
100
+
101
+ def multiple_js_imported?
102
+ imported&.keys&.many? { |x| x.end_with?(*JS_EXTENSIONS) }
103
+ end
104
+
105
+ def imported?(filepath = nil)
106
+ filepath ? imported&.key?(filepath) : !imported.blank?
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,88 @@
1
+ const elements = document.querySelectorAll("[data-proscenium-component-path]");
2
+
3
+ // Initialize only if there are components.
4
+ elements.length > 0 && init();
5
+
6
+ function init() {
7
+ /**
8
+ * Mounts component located at `path`, into the DOM `element`.
9
+ *
10
+ * The element at which the component is mounted must have the following data attributes:
11
+ *
12
+ * - `data-proscenium-component-path`: The URL path to the component's source file.
13
+ * - `data-proscenium-component-props`: JSON object of props to pass to the component.
14
+ * - `data-proscenium-component-lazy`: If present, will lazily load the component when in view
15
+ * using IntersectionObserver.
16
+ * - `data-proscenium-component-forward-children`: If the element should forward its `innerHTML`
17
+ * as the component's children prop.
18
+ */
19
+ function mount(element, path, { children, ...props }) {
20
+ // For testing and simulation of slow connections.
21
+ // const sim = new Promise((resolve) => setTimeout(resolve, 5000));
22
+
23
+ if (!(path in window.prosceniumLazyScripts)) {
24
+ throw `[proscenium/react/manager] Cannot load component ${path} (not found in prosceniumLazyScripts)`;
25
+ }
26
+
27
+ const react = import("@proscenium/react-manager/react");
28
+ const Component = import(window.prosceniumLazyScripts[path].outpath);
29
+
30
+ const forwardChildren =
31
+ "prosceniumComponentForwardChildren" in element.dataset &&
32
+ element.innerHTML !== "";
33
+
34
+ Promise.all([react, Component]).then(([r, c]) => {
35
+ if (proscenium.env.RAILS_ENV === "development") {
36
+ console.groupCollapsed(
37
+ `[proscenium/react/manager] 🔥 %o mounted!`,
38
+ path
39
+ );
40
+ console.log("props: %o", props);
41
+ console.groupEnd();
42
+ }
43
+
44
+ let component;
45
+ if (forwardChildren) {
46
+ component = r.createElement(c.default, props, element.innerHTML);
47
+ } else if (children) {
48
+ component = r.createElement(c.default, props, children);
49
+ } else {
50
+ component = r.createElement(c.default, props);
51
+ }
52
+
53
+ r.createRoot(element).render(component);
54
+ });
55
+ }
56
+
57
+ Array.from(elements, (element) => {
58
+ const path = element.dataset.prosceniumComponentPath;
59
+ const isLazy = "prosceniumComponentLazy" in element.dataset;
60
+ const props = JSON.parse(element.dataset.prosceniumComponentProps);
61
+
62
+ if (proscenium.env.RAILS_ENV === "development") {
63
+ console.groupCollapsed(
64
+ `[proscenium/react/manager] ${isLazy ? "💤" : "⚡️"} %o`,
65
+ path
66
+ );
67
+ console.log("element: %o", element);
68
+ console.log("props: %o", props);
69
+ console.groupEnd();
70
+ }
71
+
72
+ if (isLazy) {
73
+ const observer = new IntersectionObserver((entries) => {
74
+ entries.forEach((entry) => {
75
+ if (entry.isIntersecting) {
76
+ observer.unobserve(element);
77
+
78
+ mount(element, path, props);
79
+ }
80
+ });
81
+ });
82
+
83
+ observer.observe(element);
84
+ } else {
85
+ mount(element, path, props);
86
+ }
87
+ });
88
+ }
@@ -0,0 +1,2 @@
1
+ export { createElement } from "react";
2
+ export { createRoot } from "react-dom/client";