proscenium 0.14.0-x86_64-linux → 0.15.0.beta.2-x86_64-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.
@@ -2,15 +2,23 @@
2
2
 
3
3
  module Proscenium
4
4
  module Helper
5
+ def sideload_assets(value)
6
+ if value.nil?
7
+ @current_template.instance_variable_defined?(:@sideload_assets_options) &&
8
+ @current_template.remove_instance_variable(:@sideload_assets_options)
9
+ else
10
+ @current_template.instance_variable_set :@sideload_assets_options, value
11
+ end
12
+ end
13
+
14
+ # Overriden to allow regular use of javascript_include_tag and stylesheet_link_tag, while still
15
+ # building with Proscenium. It's important to note that `include_assets` will not call this, as
16
+ # those asset paths all begin with a slash, which the Rails asset helpers do not pass through to
17
+ # here.
5
18
  def compute_asset_path(path, options = {})
6
19
  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
20
+ result = Proscenium::Builder.build_to_path(path, base_url: request.base_url)
21
+ return result.split('::').last.delete_prefix 'public'
14
22
  end
15
23
 
16
24
  super
@@ -40,78 +48,23 @@ module Proscenium
40
48
  CssModule::Transformer.new(path).class_names(*names).map { |name, _| name }.join(' ')
41
49
  end
42
50
 
43
- def include_stylesheets(**options)
44
- out = []
45
- Importer.each_stylesheet(delete: true) do |path, _path_options|
46
- out << stylesheet_link_tag(path, extname: false, **options)
47
- end
48
- out.join("\n").html_safe
51
+ def include_assets
52
+ include_stylesheets + include_javascripts
53
+ end
54
+
55
+ def include_stylesheets
56
+ '<!-- [PROSCENIUM_STYLESHEETS] -->'.html_safe
49
57
  end
50
58
  alias side_load_stylesheets include_stylesheets
51
59
  deprecate side_load_stylesheets: 'Use `include_stylesheets` instead', deprecator: Deprecator.new
52
60
 
53
61
  # Includes all javascripts that have been imported and side loaded.
54
62
  #
55
- # @param extract_lazy_scripts [Boolean] if true, any lazy scripts will be extracted using
56
- # `content_for` to `:proscenium_lazy_scripts` for later use. Be sure to include this in your
57
- # page with the `declare_lazy_scripts` helper, or simply
58
- # `content_for :proscenium_lazy_scripts`.
59
63
  # @return [String] the HTML tags for the javascripts.
60
- def include_javascripts(extract_lazy_scripts: false, **options) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
61
- out = []
62
-
63
- if Rails.application.config.proscenium.code_splitting && Importer.multiple_js_imported?
64
- imports = Importer.imported.dup
65
-
66
- paths_to_build = []
67
- Importer.each_javascript(delete: true) do |x, _|
68
- paths_to_build << x.delete_prefix('/')
69
- end
70
-
71
- result = Builder.build(paths_to_build.join(';'), base_url: request.base_url)
72
-
73
- # Remove the react components from the results, so they are not side loaded. Instead they
74
- # are lazy loaded by the component manager.
75
-
76
- scripts = {}
77
- result.split(';').each do |x|
78
- inpath, outpath = x.split('::')
79
- inpath.prepend '/'
80
- outpath.delete_prefix! 'public'
81
-
82
- next unless imports.key?(inpath)
83
-
84
- if (import = imports[inpath]).delete(:lazy)
85
- scripts[inpath] = import.merge(outpath: outpath)
86
- else
87
- out << javascript_include_tag(outpath, extname: false, **options)
88
- end
89
- end
90
-
91
- if extract_lazy_scripts
92
- content_for :proscenium_lazy_scripts do
93
- tag.script type: 'application/json', id: 'prosceniumLazyScripts' do
94
- raw scripts.to_json
95
- end
96
- end
97
- else
98
- out << tag.script(type: 'application/json', id: 'prosceniumLazyScripts') do
99
- raw scripts.to_json
100
- end
101
- end
102
- else
103
- Importer.each_javascript(delete: true) do |path, _|
104
- out << javascript_include_tag(path, extname: false, **options)
105
- end
106
- end
107
-
108
- out.join("\n").html_safe
64
+ def include_javascripts
65
+ '<!-- [PROSCENIUM_LAZY_SCRIPTS] --><!-- [PROSCENIUM_JAVASCRIPTS] -->'.html_safe
109
66
  end
110
67
  alias side_load_javascripts include_javascripts
111
68
  deprecate side_load_javascripts: 'Use `include_javascripts` instead', deprecator: Deprecator.new
112
-
113
- def declare_lazy_scripts
114
- content_for :proscenium_lazy_scripts
115
- end
116
69
  end
117
70
  end
@@ -72,8 +72,8 @@ module Proscenium
72
72
  end
73
73
  end
74
74
 
75
- JS_EXTENSIONS.find(&import_if_exists)
76
- CSS_EXTENSIONS.find(&import_if_exists)
75
+ JS_EXTENSIONS.find(&import_if_exists) unless options[:js] == false
76
+ CSS_EXTENSIONS.find(&import_if_exists) unless options[:css] == false
77
77
  end
78
78
 
79
79
  def each_stylesheet(delete: false)
@@ -109,10 +109,6 @@ module Proscenium
109
109
  imported&.keys&.any? { |x| x.end_with?(*JS_EXTENSIONS) }
110
110
  end
111
111
 
112
- def multiple_js_imported?
113
- imported&.keys&.many? { |x| x.end_with?(*JS_EXTENSIONS) }
114
- end
115
-
116
112
  def imported?(filepath = nil)
117
113
  filepath ? imported&.key?(filepath) : !imported.blank?
118
114
  end
@@ -1,18 +1,70 @@
1
1
  window.Proscenium = window.Proscenium || { lazyScripts: {} };
2
+ const pathAttribute = "data-proscenium-component-path";
2
3
 
4
+ // Find lazyscripts JSON already in the DOM.
3
5
  const element = document.querySelector("#prosceniumLazyScripts");
4
6
  if (element) {
5
- const scripts = JSON.parse(element.text);
6
7
  window.Proscenium.lazyScripts = {
7
8
  ...window.Proscenium.lazyScripts,
8
- ...scripts,
9
+ ...JSON.parse(element.text),
9
10
  };
10
11
  }
11
12
 
12
- const elements = document.querySelectorAll("[data-proscenium-component-path]");
13
+ // Find components already in the DOM.
14
+ const elements = document.querySelectorAll(`[${pathAttribute}]`);
13
15
  elements.length > 0 && init(elements);
14
16
 
15
- function init() {
17
+ new MutationObserver((mutationsList) => {
18
+ for (const { addedNodes } of mutationsList) {
19
+ for (const ele of addedNodes) {
20
+ if (ele.tagName === "SCRIPT" && ele.id === "prosceniumLazyScripts") {
21
+ window.Proscenium.lazyScripts = {
22
+ ...window.Proscenium.lazyScripts,
23
+ ...JSON.parse(ele.text),
24
+ };
25
+ } else if (ele.matches(`[${pathAttribute}]`)) {
26
+ init([ele]);
27
+ }
28
+ }
29
+ }
30
+ }).observe(document, {
31
+ subtree: true,
32
+ childList: true,
33
+ });
34
+
35
+ function init(elements) {
36
+ Array.from(elements, (element) => {
37
+ const path = element.dataset.prosceniumComponentPath;
38
+ const isLazy = "prosceniumComponentLazy" in element.dataset;
39
+ const props = JSON.parse(element.dataset.prosceniumComponentProps);
40
+
41
+ if (proscenium.env.RAILS_ENV === "development") {
42
+ console.groupCollapsed(
43
+ `[proscenium/react/manager] ${isLazy ? "💤" : "⚡️"} %o`,
44
+ path
45
+ );
46
+ console.log("element: %o", element);
47
+ console.log("props: %o", props);
48
+ console.groupEnd();
49
+ }
50
+
51
+ if (isLazy) {
52
+ const observer = new IntersectionObserver((entries) => {
53
+ entries.forEach((entry) => {
54
+ if (entry.isIntersecting) {
55
+ observer.unobserve(element);
56
+
57
+ mount(element, path, props);
58
+ }
59
+ });
60
+ });
61
+
62
+ observer.observe(element);
63
+ } else {
64
+ mount(element, path, props);
65
+ }
66
+ });
67
+
16
68
  /**
17
69
  * Mounts component located at `path`, into the DOM `element`.
18
70
  *
@@ -66,36 +118,4 @@ function init() {
66
118
  console.error("[proscenium/react/manager] %o - %o", path, error);
67
119
  });
68
120
  }
69
-
70
- Array.from(elements, (element) => {
71
- const path = element.dataset.prosceniumComponentPath;
72
- const isLazy = "prosceniumComponentLazy" in element.dataset;
73
- const props = JSON.parse(element.dataset.prosceniumComponentProps);
74
-
75
- if (proscenium.env.RAILS_ENV === "development") {
76
- console.groupCollapsed(
77
- `[proscenium/react/manager] ${isLazy ? "💤" : "⚡️"} %o`,
78
- path
79
- );
80
- console.log("element: %o", element);
81
- console.log("props: %o", props);
82
- console.groupEnd();
83
- }
84
-
85
- if (isLazy) {
86
- const observer = new IntersectionObserver((entries) => {
87
- entries.forEach((entry) => {
88
- if (entry.isIntersecting) {
89
- observer.unobserve(element);
90
-
91
- mount(element, path, props);
92
- }
93
- });
94
- });
95
-
96
- observer.observe(element);
97
- } else {
98
- mount(element, path, props);
99
- }
100
- });
101
121
  }
@@ -10,12 +10,24 @@ module Proscenium
10
10
  end
11
11
  end
12
12
 
13
- def build(event)
13
+ def build_to_path(event)
14
14
  path = event.payload[:identifier]
15
+ cached = event.payload[:cached] ? ' | Cached!' : ''
15
16
  path = CGI.unescape(path) if path.start_with?(/https?%3A%2F%2F/)
16
17
 
17
18
  info do
18
- message = +"[Proscenium] Building #{path}"
19
+ message = +" #{color('[Proscenium]', nil, bold: true)} Building (to path) #{path}"
20
+ message << " (Duration: #{event.duration.round(1)}ms | " \
21
+ "Allocations: #{event.allocations}#{cached})"
22
+ end
23
+ end
24
+
25
+ def build_to_string(event)
26
+ path = event.payload[:identifier]
27
+ path = CGI.unescape(path) if path.start_with?(/https?%3A%2F%2F/)
28
+
29
+ info do
30
+ message = +" #{color('[Proscenium]', nil, bold: true)} Building #{path}"
19
31
  message << " (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
20
32
  end
21
33
  end
@@ -20,8 +20,8 @@ module Proscenium
20
20
  end
21
21
 
22
22
  def attempt
23
- render_response Builder.build(path_to_build, root: Rails.root.to_s,
24
- base_url: @request.base_url)
23
+ render_response Builder.build_to_string(path_to_build, root: Rails.root.to_s,
24
+ base_url: @request.base_url)
25
25
  rescue Builder::CompileError => e
26
26
  raise self.class::CompileError, { file: @request.fullpath, detail: e.message }, caller
27
27
  end
@@ -3,42 +3,52 @@
3
3
  module Proscenium
4
4
  # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
5
5
  module Monkey
6
- module DebugView
7
- def initialize(assigns)
8
- paths = [RESCUES_TEMPLATE_PATH, Rails.root.join('lib', 'templates').to_s]
9
- lookup_context = ActionView::LookupContext.new(paths)
10
- super(lookup_context, assigns, nil)
11
- end
12
- end
13
-
14
6
  module TemplateRenderer
15
7
  private
16
8
 
17
- def render_template(view, template, layout_name, locals)
18
- return super unless Proscenium.config.side_load
9
+ def render_template(view, template, layout_name, locals) # rubocop:disable Metrics/*
10
+ result = super
11
+ return result if !view.controller || !Proscenium.config.side_load
19
12
 
20
- layout = find_layout(layout_name, locals.keys, [formats.first])
21
13
  renderable = template.instance_variable_get(:@renderable)
22
14
 
23
- if Object.const_defined?(:ViewComponent) &&
24
- template.is_a?(ActionView::Template::Renderable) &&
25
- renderable.class < ::ViewComponent::Base && renderable.class.format == :html
26
- # Side load controller rendered ViewComponent
27
- Importer.sideload "app/views/#{layout.virtual_path}" if layout
28
- Importer.sideload "app/views/#{renderable.virtual_path}"
29
- elsif template.respond_to?(:virtual_path) &&
30
- template.respond_to?(:type) && template.type == :html
31
- Importer.sideload "app/views/#{layout.virtual_path}" if layout
32
-
33
- # Try side loading the variant template
34
- if template.respond_to?(:variant) && template.variant
35
- Importer.sideload "app/views/#{template.virtual_path}+#{template.variant}"
36
- end
37
-
38
- Importer.sideload "app/views/#{template.virtual_path}"
15
+ to_sideload = if Object.const_defined?(:ViewComponent) &&
16
+ template.is_a?(ActionView::Template::Renderable) &&
17
+ renderable.class < ::ViewComponent::Base &&
18
+ renderable.class.format == :html
19
+ renderable
20
+ elsif template.respond_to?(:virtual_path) &&
21
+ template.respond_to?(:type) && template.type == :html
22
+ template
23
+ end
24
+ if to_sideload
25
+ options = view.controller.sideload_assets_options
26
+ layout = find_layout(layout_name, locals.keys, [formats.first])
27
+ sideload_template_assets layout, view.controller, options if layout
28
+ sideload_template_assets to_sideload, view.controller, options
29
+ end
30
+
31
+ result
32
+ end
33
+
34
+ def sideload_template_assets(tpl, controller, options)
35
+ options = {} if options.nil?
36
+ options = { js: options, css: options } unless options.is_a?(Hash)
37
+
38
+ if tpl.instance_variable_defined?(:@sideload_assets_options)
39
+ tpl_options = tpl.instance_variable_get(:@sideload_assets_options)
40
+ options = case tpl_options
41
+ when Hash then options.deep_merge(tpl_options)
42
+ else
43
+ { js: tpl_options, css: tpl_options }
44
+ end
45
+ end
46
+
47
+ %i[css js].each do |k|
48
+ options[k] = controller.instance_eval(&options[k]) if options[k].is_a?(Proc)
39
49
  end
40
50
 
41
- super
51
+ Importer.sideload "app/views/#{tpl.virtual_path}", **options
42
52
  end
43
53
  end
44
54
 
@@ -46,13 +56,38 @@ module Proscenium
46
56
  private
47
57
 
48
58
  def render_partial_template(view, locals, template, layout, block)
49
- if Proscenium.config.side_load && template.respond_to?(:virtual_path) &&
59
+ result = super
60
+
61
+ return result if !view.controller || !Proscenium.config.side_load
62
+
63
+ if template.respond_to?(:virtual_path) &&
50
64
  template.respond_to?(:type) && template.type == :html
51
- Importer.sideload "app/views/#{layout.virtual_path}" if layout
52
- Importer.sideload "app/views/#{template.virtual_path}"
65
+ options = view.controller.sideload_assets_options
66
+ sideload_template_assets layout, options if layout
67
+ sideload_template_assets template, options
68
+ end
69
+
70
+ result
71
+ end
72
+
73
+ def sideload_template_assets(tpl, options)
74
+ options = {} if options.nil?
75
+ options = { js: options, css: options } unless options.is_a?(Hash)
76
+
77
+ if tpl.instance_variable_defined?(:@sideload_assets_options)
78
+ tpl_options = tpl.instance_variable_get(:@sideload_assets_options)
79
+ options = if tpl_options.is_a?(Hash)
80
+ options.deep_merge tpl_options
81
+ else
82
+ { js: tpl_options, css: tpl_options }
83
+ end
84
+ end
85
+
86
+ %i[css js].each do |k|
87
+ options[k] = controller.instance_eval(&options[k]) if options[k].is_a?(Proc)
53
88
  end
54
89
 
55
- super
90
+ Importer.sideload "app/views/#{tpl.virtual_path}", **options
56
91
  end
57
92
  end
58
93
  end
@@ -1,96 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Proscenium::Phlex::AssetInclusions
4
- include Phlex::Rails::Helpers::ContentFor
5
- include Phlex::Rails::Helpers::StyleSheetPath
6
- include Phlex::Rails::Helpers::JavaScriptPath
7
-
8
4
  def include_stylesheets
9
5
  comment { '[PROSCENIUM_STYLESHEETS]' }
10
6
  end
11
7
 
12
- def include_javascripts(defer_lazy_scripts: false)
13
- comment { '[PROSCENIUM_JAVASCRIPTS]' }
14
- !defer_lazy_scripts && include_lazy_javascripts
15
- end
16
-
17
- def include_lazy_javascripts
8
+ def include_javascripts
18
9
  comment { '[PROSCENIUM_LAZY_SCRIPTS]' }
10
+ comment { '[PROSCENIUM_JAVASCRIPTS]' }
19
11
  end
20
12
 
21
- def include_assets(defer_lazy_scripts: false)
13
+ def include_assets
22
14
  include_stylesheets
23
- include_javascripts(defer_lazy_scripts: defer_lazy_scripts)
24
- end
25
-
26
- def after_template
27
- super
28
-
29
- @_buffer.gsub! '<!-- [PROSCENIUM_STYLESHEETS] -->', capture_stylesheets!
30
- @_buffer.gsub! '<!-- [PROSCENIUM_JAVASCRIPTS] -->', capture_javascripts!
31
-
32
- if content_for?(:proscenium_lazy_scripts)
33
- flush
34
- @_buffer.gsub!('<!-- [PROSCENIUM_LAZY_SCRIPTS] -->', capture do
35
- content_for(:proscenium_lazy_scripts)
36
- end)
37
- else
38
- @_buffer.gsub! '<!-- [PROSCENIUM_LAZY_SCRIPTS] -->', ''
39
- end
40
- end
41
-
42
- private
43
-
44
- def capture_stylesheets!
45
- capture do
46
- Proscenium::Importer.each_stylesheet(delete: true) do |path, _path_options|
47
- link rel: 'stylesheet', href: stylesheet_path(path, extname: false)
48
- end
49
- end
50
- end
51
-
52
- def capture_javascripts! # rubocop:disable Metrics/*
53
- unless Rails.application.config.proscenium.code_splitting &&
54
- Proscenium::Importer.multiple_js_imported?
55
- return capture do
56
- Proscenium::Importer.each_javascript(delete: true) do |path, _|
57
- script(src: javascript_path(path, extname: false), type: :module)
58
- end
59
- end
60
- end
61
-
62
- imports = Proscenium::Importer.imported.dup
63
- paths_to_build = []
64
- Proscenium::Importer.each_javascript(delete: true) do |x, _|
65
- paths_to_build << x.delete_prefix('/')
66
- end
67
-
68
- result = Proscenium::Builder.build(paths_to_build.join(';'), base_url: helpers.request.base_url)
69
-
70
- # Remove the react components from the results, so they are not side loaded. Instead they
71
- # are lazy loaded by the component manager.
72
-
73
- capture do
74
- scripts = {}
75
- result.split(';').each do |x|
76
- inpath, outpath = x.split('::')
77
- inpath.prepend '/'
78
- outpath.delete_prefix! 'public'
79
-
80
- next unless imports.key?(inpath)
81
-
82
- if (import = imports[inpath]).delete(:lazy)
83
- scripts[inpath] = import.merge(outpath: outpath)
84
- else
85
- script(src: javascript_path(outpath, extname: false), type: :module)
86
- end
87
- end
88
-
89
- content_for :proscenium_lazy_scripts do
90
- script type: 'application/json', id: 'prosceniumLazyScripts' do
91
- unsafe_raw scripts.to_json
92
- end
93
- end
94
- end
15
+ include_javascripts
95
16
  end
96
17
  end
@@ -12,15 +12,19 @@ module Proscenium
12
12
 
13
13
  include Proscenium::SourcePath
14
14
  include CssModules
15
+ include AssetInclusions
15
16
 
16
17
  module Sideload
17
18
  def before_template
18
- Proscenium::SideLoad.sideload_inheritance_chain self
19
+ Proscenium::SideLoad.sideload_inheritance_chain self,
20
+ helpers.controller.sideload_assets_options
19
21
 
20
22
  super
21
23
  end
22
24
  end
23
25
 
26
+ class_attribute :sideload_assets_options
27
+
24
28
  class << self
25
29
  attr_accessor :abstract_class
26
30
 
@@ -29,6 +33,10 @@ module Proscenium
29
33
 
30
34
  super
31
35
  end
36
+
37
+ def sideload_assets(value)
38
+ self.sideload_assets_options = value
39
+ end
32
40
  end
33
41
  end
34
42
  end
@@ -6,12 +6,6 @@ require 'proscenium/log_subscriber'
6
6
  ENV['RAILS_ENV'] = Rails.env
7
7
 
8
8
  module Proscenium
9
- class << self
10
- def config
11
- @config ||= Railtie.config.proscenium
12
- end
13
- end
14
-
15
9
  class Railtie < ::Rails::Engine
16
10
  isolate_namespace Proscenium
17
11
 
@@ -20,6 +14,10 @@ module Proscenium
20
14
  config.proscenium.side_load = true
21
15
  config.proscenium.code_splitting = true
22
16
 
17
+ # Cache asset paths when building to path. Enabled by default in production.
18
+ # @see Proscenium::Builder#build_to_path
19
+ config.proscenium.cache = ActiveSupport::Cache::MemoryStore.new if Rails.env.production?
20
+
23
21
  # TODO: implement!
24
22
  config.proscenium.cache_query_string = Rails.env.production? && ENV.fetch('REVISION', nil)
25
23
  config.proscenium.cache_max_age = 2_592_000 # 30 days
@@ -45,6 +43,12 @@ module Proscenium
45
43
  'Proscenium::Builder::BuildError' => 'build_error'
46
44
  }
47
45
 
46
+ config.after_initialize do |_app|
47
+ ActiveSupport.on_load(:action_view) do
48
+ include Proscenium::Helper
49
+ end
50
+ end
51
+
48
52
  initializer 'proscenium.debugging' do
49
53
  if Rails.gem_version >= Gem::Version.new('7.1.0')
50
54
  tpl_path = root.join('lib', 'proscenium', 'templates').to_s
@@ -58,20 +62,17 @@ module Proscenium
58
62
  app.middleware.insert_after ActionDispatch::Static, Rack::ConditionalGet
59
63
  end
60
64
 
61
- initializer 'proscenium.monkey_patches' do
62
- ActiveSupport.on_load(:action_view) do
63
- ActionView::TemplateRenderer.prepend Monkey::TemplateRenderer
64
- ActionView::PartialRenderer.prepend Monkey::PartialRenderer
65
+ initializer 'proscenium.sideloading' do
66
+ ActiveSupport.on_load(:action_controller) do
67
+ ActionController::Base.include EnsureLoaded
68
+ ActionController::Base.include SideLoad::Controller
65
69
  end
66
70
  end
67
71
 
68
- initializer 'proscenium.helper' do
72
+ initializer 'proscenium.monkey_patches' do
69
73
  ActiveSupport.on_load(:action_view) do
70
- ActionView::Base.include Helper
71
- end
72
-
73
- ActiveSupport.on_load(:action_controller) do
74
- ActionController::Base.include EnsureLoaded
74
+ ActionView::TemplateRenderer.prepend Monkey::TemplateRenderer
75
+ ActionView::PartialRenderer.prepend Monkey::PartialRenderer
75
76
  end
76
77
  end
77
78
  end
@@ -44,9 +44,9 @@ module Proscenium
44
44
  end
45
45
 
46
46
  class_methods do
47
- def sideload
48
- Importer.import manager
49
- Importer.sideload source_path, lazy: true
47
+ def sideload(options)
48
+ Importer.import manager, **options, js: { type: 'module' }
49
+ Importer.sideload source_path, lazy: true, **options
50
50
  end
51
51
  end
52
52