proscenium 0.14.0-x86_64-darwin → 0.15.0.beta.2-x86_64-darwin

Sign up to get free protection for your applications and to get access to all the features.
@@ -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