proscenium 0.10.0-arm64-darwin → 0.11.0-arm64-darwin
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 +4 -4
- data/README.md +207 -45
- data/lib/proscenium/builder.rb +41 -19
- data/lib/proscenium/css_module/path.rb +31 -0
- data/lib/proscenium/css_module/transformer.rb +82 -0
- data/lib/proscenium/css_module.rb +12 -25
- data/lib/proscenium/ensure_loaded.rb +27 -0
- data/lib/proscenium/ext/proscenium +0 -0
- data/lib/proscenium/ext/proscenium.h +3 -2
- data/lib/proscenium/helper.rb +85 -0
- data/lib/proscenium/importer.rb +110 -0
- data/lib/proscenium/libs/react-manager/index.jsx +101 -0
- data/lib/proscenium/libs/react-manager/react.js +2 -0
- data/lib/proscenium/libs/test.js +1 -0
- data/lib/proscenium/middleware/base.rb +7 -7
- data/lib/proscenium/middleware/engines.rb +37 -0
- data/lib/proscenium/middleware/esbuild.rb +3 -5
- data/lib/proscenium/middleware/runtime.rb +18 -0
- data/lib/proscenium/middleware.rb +14 -4
- data/lib/proscenium/{side_load/monkey.rb → monkey.rb} +19 -15
- data/lib/proscenium/phlex/{resolve_css_modules.rb → css_modules.rb} +28 -16
- data/lib/proscenium/phlex/react_component.rb +26 -27
- data/lib/proscenium/phlex.rb +11 -30
- data/lib/proscenium/railtie.rb +44 -46
- data/lib/proscenium/react_componentable.rb +95 -0
- data/lib/proscenium/resolver.rb +37 -0
- data/lib/proscenium/side_load.rb +13 -73
- data/lib/proscenium/source_path.rb +15 -0
- data/lib/proscenium/templates/rescues/build_error.html.erb +30 -0
- data/lib/proscenium/utils.rb +13 -0
- data/lib/proscenium/version.rb +1 -1
- data/lib/proscenium/view_component/css_modules.rb +11 -0
- data/lib/proscenium/view_component/react_component.rb +15 -15
- data/lib/proscenium/view_component/sideload.rb +4 -0
- data/lib/proscenium/view_component.rb +8 -38
- data/lib/proscenium.rb +22 -68
- metadata +23 -30
- data/lib/proscenium/componentable.rb +0 -63
- data/lib/proscenium/css_module/class_names_resolver.rb +0 -66
- data/lib/proscenium/css_module/resolver.rb +0 -76
- data/lib/proscenium/current.rb +0 -9
- data/lib/proscenium/phlex/component_concerns.rb +0 -9
- data/lib/proscenium/phlex/page.rb +0 -62
- data/lib/proscenium/side_load/ensure_loaded.rb +0 -25
- data/lib/proscenium/side_load/helper.rb +0 -41
- data/lib/proscenium/view_component/tag_builder.rb +0 -23
@@ -3,41 +3,28 @@
|
|
3
3
|
module Proscenium::CssModule
|
4
4
|
extend ActiveSupport::Autoload
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
@pathname = pathname
|
9
|
-
super
|
10
|
-
end
|
11
|
-
|
12
|
-
def message
|
13
|
-
"Stylesheet is required, but does not exist: #{@pathname}"
|
14
|
-
end
|
15
|
-
end
|
6
|
+
autoload :Path
|
7
|
+
autoload :Transformer
|
16
8
|
|
17
|
-
|
18
|
-
|
9
|
+
class TransformError < StandardError
|
10
|
+
def initialize(name, additional_msg = nil)
|
11
|
+
msg = "Failed to transform CSS module `#{name}`"
|
12
|
+
msg << ' - ' << additional_msg if additional_msg
|
19
13
|
|
20
|
-
|
21
|
-
|
22
|
-
# @param name [Array, String]
|
23
|
-
def css_module!(names)
|
24
|
-
cssm.class_names!(names).join ' '
|
14
|
+
super msg
|
15
|
+
end
|
25
16
|
end
|
26
17
|
|
27
18
|
# Accepts one or more CSS class names, and transforms them into CSS module names.
|
28
19
|
#
|
29
|
-
# @param name [Array,
|
30
|
-
def css_module(names)
|
31
|
-
cssm.class_names(names).join
|
20
|
+
# @param name [String,Symbol,Array<String,Symbol>]
|
21
|
+
def css_module(*names)
|
22
|
+
cssm.class_names(*names, require_prefix: false).map { |name, _| name }.join(' ')
|
32
23
|
end
|
33
24
|
|
34
25
|
private
|
35
26
|
|
36
|
-
def path
|
37
|
-
self.class.path
|
38
|
-
end
|
39
|
-
|
40
27
|
def cssm
|
41
|
-
@cssm ||=
|
28
|
+
@cssm ||= Transformer.new(self.class.css_module_path)
|
42
29
|
end
|
43
30
|
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
|
@@ -97,7 +97,7 @@ extern "C" {
|
|
97
97
|
// - codeSpitting?
|
98
98
|
// - debug?
|
99
99
|
//
|
100
|
-
extern struct Result build(char* filepath, char* baseUrl, char* importMap, char* envVars, char*
|
100
|
+
extern struct Result build(char* filepath, char* baseUrl, char* importMap, char* envVars, char* appRoot, char* gemPath, unsigned int env, GoUint8 codeSplitting, char* engines, GoUint8 debug);
|
101
101
|
|
102
102
|
// Resolve the given `path` relative to the `root`.
|
103
103
|
//
|
@@ -107,8 +107,9 @@ extern struct Result build(char* filepath, char* baseUrl, char* importMap, char*
|
|
107
107
|
// Config
|
108
108
|
// - root - The working directory.
|
109
109
|
// - env - The environment (1 = development, 2 = test, 3 = production)
|
110
|
+
// - debug?
|
110
111
|
//
|
111
|
-
extern struct Result resolve(char* path, char* importMap, char*
|
112
|
+
extern struct Result resolve(char* path, char* importMap, char* appRoot, char* gemPath, unsigned int env, GoUint8 debug);
|
112
113
|
|
113
114
|
#ifdef __cplusplus
|
114
115
|
}
|
data/lib/proscenium/helper.rb
CHANGED
@@ -15,5 +15,90 @@ 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).map do |name, _|
|
26
|
+
name
|
27
|
+
end.join(' ')
|
28
|
+
end
|
29
|
+
|
30
|
+
def include_stylesheets(**options)
|
31
|
+
out = []
|
32
|
+
Importer.each_stylesheet(delete: true) do |path, _path_options|
|
33
|
+
out << stylesheet_link_tag(path, extname: false, **options)
|
34
|
+
end
|
35
|
+
out.join("\n").html_safe
|
36
|
+
end
|
37
|
+
alias side_load_stylesheets include_stylesheets
|
38
|
+
deprecate side_load_stylesheets: 'Use `include_stylesheets` instead', deprecator: Deprecator.new
|
39
|
+
|
40
|
+
# Includes all javascripts that have been imported and side loaded.
|
41
|
+
#
|
42
|
+
# @param extract_lazy_scripts [Boolean] if true, any lazy scripts will be extracted using
|
43
|
+
# `content_for` to `:proscenium_lazy_scripts` for later use. Be sure to include this in your
|
44
|
+
# page with the `declare_lazy_scripts` helper, or simply
|
45
|
+
# `content_for :proscenium_lazy_scripts`.
|
46
|
+
# @return [String] the HTML tags for the javascripts.
|
47
|
+
def include_javascripts(extract_lazy_scripts: false, **options) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
48
|
+
out = []
|
49
|
+
|
50
|
+
if Rails.application.config.proscenium.code_splitting && Importer.multiple_js_imported?
|
51
|
+
imports = Importer.imported.dup
|
52
|
+
|
53
|
+
paths_to_build = []
|
54
|
+
Importer.each_javascript(delete: true) do |x, _|
|
55
|
+
paths_to_build << x.delete_prefix('/')
|
56
|
+
end
|
57
|
+
|
58
|
+
result = Builder.build(paths_to_build.join(';'), base_url: request.base_url)
|
59
|
+
|
60
|
+
# Remove the react components from the results, so they are not side loaded. Instead they
|
61
|
+
# are lazy loaded by the component manager.
|
62
|
+
|
63
|
+
scripts = {}
|
64
|
+
result.split(';').each do |x|
|
65
|
+
inpath, outpath = x.split('::')
|
66
|
+
inpath.prepend '/'
|
67
|
+
outpath.delete_prefix! 'public'
|
68
|
+
|
69
|
+
next unless imports.key?(inpath)
|
70
|
+
|
71
|
+
if (import = imports[inpath]).delete(:lazy)
|
72
|
+
scripts[inpath] = import.merge(outpath: outpath)
|
73
|
+
else
|
74
|
+
out << javascript_include_tag(outpath, extname: false, **options)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
if extract_lazy_scripts
|
79
|
+
content_for :proscenium_lazy_scripts do
|
80
|
+
tag.script type: 'application/json', id: 'prosceniumLazyScripts' do
|
81
|
+
raw scripts.to_json
|
82
|
+
end
|
83
|
+
end
|
84
|
+
else
|
85
|
+
out << tag.script(type: 'application/json', id: 'prosceniumLazyScripts') do
|
86
|
+
raw scripts.to_json
|
87
|
+
end
|
88
|
+
end
|
89
|
+
else
|
90
|
+
Importer.each_javascript(delete: true) do |path, _|
|
91
|
+
out << javascript_include_tag(path, extname: false, **options)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
out.join("\n").html_safe
|
96
|
+
end
|
97
|
+
alias side_load_javascripts include_javascripts
|
98
|
+
deprecate side_load_javascripts: 'Use `include_javascripts` instead', deprecator: Deprecator.new
|
99
|
+
|
100
|
+
def declare_lazy_scripts
|
101
|
+
content_for :proscenium_lazy_scripts
|
102
|
+
end
|
18
103
|
end
|
19
104
|
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 URL 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,101 @@
|
|
1
|
+
window.Proscenium = window.Proscenium || { lazyScripts: {} };
|
2
|
+
|
3
|
+
const element = document.querySelector("#prosceniumLazyScripts");
|
4
|
+
if (element) {
|
5
|
+
const scripts = JSON.parse(element.text);
|
6
|
+
window.Proscenium.lazyScripts = {
|
7
|
+
...window.Proscenium.lazyScripts,
|
8
|
+
...scripts,
|
9
|
+
};
|
10
|
+
}
|
11
|
+
|
12
|
+
const elements = document.querySelectorAll("[data-proscenium-component-path]");
|
13
|
+
elements.length > 0 && init(elements);
|
14
|
+
|
15
|
+
function init() {
|
16
|
+
/**
|
17
|
+
* Mounts component located at `path`, into the DOM `element`.
|
18
|
+
*
|
19
|
+
* The element at which the component is mounted must have the following data attributes:
|
20
|
+
*
|
21
|
+
* - `data-proscenium-component-path`: The URL path to the component's source file.
|
22
|
+
* - `data-proscenium-component-props`: JSON object of props to pass to the component.
|
23
|
+
* - `data-proscenium-component-lazy`: If present, will lazily load the component when in view
|
24
|
+
* using IntersectionObserver.
|
25
|
+
* - `data-proscenium-component-forward-children`: If the element should forward its `innerHTML`
|
26
|
+
* as the component's children prop.
|
27
|
+
*/
|
28
|
+
function mount(element, path, { children, ...props }) {
|
29
|
+
// For testing and simulation of slow connections.
|
30
|
+
// const sim = new Promise((resolve) => setTimeout(resolve, 5000));
|
31
|
+
|
32
|
+
if (!window.Proscenium.lazyScripts[path]) {
|
33
|
+
throw `[proscenium/react/manager] Cannot load component ${path} (not found in Proscenium.lazyScripts)`;
|
34
|
+
}
|
35
|
+
|
36
|
+
const react = import("@proscenium/react-manager/react");
|
37
|
+
const Component = import(window.Proscenium.lazyScripts[path].outpath);
|
38
|
+
|
39
|
+
const forwardChildren =
|
40
|
+
"prosceniumComponentForwardChildren" in element.dataset &&
|
41
|
+
element.innerHTML !== "";
|
42
|
+
|
43
|
+
Promise.all([react, Component])
|
44
|
+
.then(([r, c]) => {
|
45
|
+
if (proscenium.env.RAILS_ENV === "development") {
|
46
|
+
console.groupCollapsed(
|
47
|
+
`[proscenium/react/manager] 🔥 %o mounted!`,
|
48
|
+
path
|
49
|
+
);
|
50
|
+
console.log("props: %o", props);
|
51
|
+
console.groupEnd();
|
52
|
+
}
|
53
|
+
|
54
|
+
let component;
|
55
|
+
if (forwardChildren) {
|
56
|
+
component = r.createElement(c.default, props, element.innerHTML);
|
57
|
+
} else if (children) {
|
58
|
+
component = r.createElement(c.default, props, children);
|
59
|
+
} else {
|
60
|
+
component = r.createElement(c.default, props);
|
61
|
+
}
|
62
|
+
|
63
|
+
r.createRoot(element).render(component);
|
64
|
+
})
|
65
|
+
.catch((error) => {
|
66
|
+
console.error("[proscenium/react/manager] %o - %o", path, error);
|
67
|
+
});
|
68
|
+
}
|
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
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
console.log("/@proscenium/test.js");
|
@@ -34,12 +34,12 @@ module Proscenium
|
|
34
34
|
private
|
35
35
|
|
36
36
|
def real_path
|
37
|
-
@request.path
|
37
|
+
@real_path ||= @request.path
|
38
38
|
end
|
39
39
|
|
40
40
|
# @return [String] the path to the file without the leading slash which will be built.
|
41
41
|
def path_to_build
|
42
|
-
@request.path[1..]
|
42
|
+
@path_to_build ||= @request.path[1..]
|
43
43
|
end
|
44
44
|
|
45
45
|
def sourcemap?
|
@@ -53,22 +53,22 @@ module Proscenium
|
|
53
53
|
def file_readable?
|
54
54
|
return false unless (path = clean_path(sourcemap? ? real_path[0...-4] : real_path))
|
55
55
|
|
56
|
-
file_stat = File.stat(
|
56
|
+
file_stat = File.stat(root_for_readable.join(path.delete_prefix('/').b).to_s)
|
57
57
|
rescue SystemCallError
|
58
58
|
false
|
59
59
|
else
|
60
60
|
file_stat.file? && file_stat.readable?
|
61
61
|
end
|
62
62
|
|
63
|
+
def root_for_readable
|
64
|
+
Rails.root
|
65
|
+
end
|
66
|
+
|
63
67
|
def clean_path(file)
|
64
68
|
path = Rack::Utils.unescape_path file.chomp('/').delete_prefix('/')
|
65
69
|
Rack::Utils.clean_path_info path if Rack::Utils.valid_path? path
|
66
70
|
end
|
67
71
|
|
68
|
-
def root
|
69
|
-
@root ||= Rails.root.to_s
|
70
|
-
end
|
71
|
-
|
72
72
|
def content_type
|
73
73
|
case ::File.extname(path_to_build)
|
74
74
|
when '.js', '.mjs', '.ts', '.tsx', '.jsx' then 'application/javascript'
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Proscenium
|
4
|
+
class Middleware
|
5
|
+
# This middleware handles requests for assets in Rails engines. An engine that wants to expose
|
6
|
+
# its assets via Proscenium to the application must add itself to the list of engines in the
|
7
|
+
# Proscenium config options `Proscenium.config.engines`.
|
8
|
+
#
|
9
|
+
# For example, we have a gem that exposes a Rails engine.
|
10
|
+
#
|
11
|
+
# module Gem1
|
12
|
+
# class Engine < ::Rails::Engine
|
13
|
+
# config.proscenium.engines << self
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# When this gem is installed in any Rails application, its assets will be available at the URL
|
18
|
+
# `/gem1/...`. For example, if the gem has a file `lib/styles.css`, it can be requested at
|
19
|
+
# `/gem1/lib/styles.css`.
|
20
|
+
#
|
21
|
+
class Engines < Esbuild
|
22
|
+
def real_path
|
23
|
+
@real_path ||= Pathname.new(@request.path.delete_prefix("/#{engine.engine_name}")).to_s
|
24
|
+
end
|
25
|
+
|
26
|
+
def root_for_readable
|
27
|
+
engine.root
|
28
|
+
end
|
29
|
+
|
30
|
+
def engine
|
31
|
+
@engine ||= Proscenium.config.engines.find do |engine|
|
32
|
+
@request.path.start_with?("/#{engine.engine_name}")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -20,11 +20,9 @@ module Proscenium
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def attempt
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
end
|
27
|
-
rescue Proscenium::Builder::CompileError => e
|
23
|
+
render_response Builder.build(path_to_build, root: Rails.root.to_s,
|
24
|
+
base_url: @request.base_url)
|
25
|
+
rescue Builder::CompileError => e
|
28
26
|
raise self.class::CompileError, { file: @request.fullpath, detail: e.message }, caller
|
29
27
|
end
|
30
28
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Proscenium
|
4
|
+
class Middleware
|
5
|
+
class Runtime < Esbuild
|
6
|
+
private
|
7
|
+
|
8
|
+
def real_path
|
9
|
+
@real_path ||= Pathname.new(@request.path.sub(%r{^/@proscenium},
|
10
|
+
'/lib/proscenium/libs')).to_s
|
11
|
+
end
|
12
|
+
|
13
|
+
def root_for_readable
|
14
|
+
Proscenium::Railtie.root
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -9,6 +9,8 @@ module Proscenium
|
|
9
9
|
|
10
10
|
autoload :Base
|
11
11
|
autoload :Esbuild
|
12
|
+
autoload :Engines
|
13
|
+
autoload :Runtime
|
12
14
|
autoload :Url
|
13
15
|
|
14
16
|
def initialize(app)
|
@@ -40,12 +42,20 @@ module Proscenium
|
|
40
42
|
|
41
43
|
def find_type(request)
|
42
44
|
return Url if request.path.match?(%r{^/https?%3A%2F%2F})
|
43
|
-
return
|
45
|
+
return Runtime if request.path.match?(%r{^/@proscenium/})
|
46
|
+
return Esbuild if Pathname.new(request.path).fnmatch?(app_path_glob, File::FNM_EXTGLOB)
|
47
|
+
|
48
|
+
Engines if Pathname.new(request.path).fnmatch?(engines_path_glob, File::FNM_EXTGLOB)
|
49
|
+
end
|
50
|
+
|
51
|
+
def app_path_glob
|
52
|
+
"/{#{Proscenium::ALLOWED_DIRECTORIES}}/**.{#{FILE_EXTENSIONS.join(',')}}"
|
44
53
|
end
|
45
54
|
|
46
|
-
def
|
47
|
-
|
48
|
-
|
55
|
+
def engines_path_glob
|
56
|
+
names = Proscenium.config.engines.map(&:engine_name)
|
57
|
+
|
58
|
+
"/{#{names.join(',')}}/{#{Proscenium::ALLOWED_DIRECTORIES}}/**.{#{FILE_EXTENSIONS.join(',')}}"
|
49
59
|
end
|
50
60
|
|
51
61
|
# TODO: handle precompiled assets
|
@@ -1,12 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
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
|
+
|
6
14
|
module TemplateRenderer
|
7
15
|
private
|
8
16
|
|
9
17
|
def render_template(view, template, layout_name, locals)
|
18
|
+
return super unless Proscenium.config.side_load
|
19
|
+
|
10
20
|
layout = find_layout(layout_name, locals.keys, [formats.first])
|
11
21
|
renderable = template.instance_variable_get(:@renderable)
|
12
22
|
|
@@ -14,18 +24,18 @@ class Proscenium::SideLoad
|
|
14
24
|
template.is_a?(ActionView::Template::Renderable) &&
|
15
25
|
renderable.class < ::ViewComponent::Base && renderable.class.format == :html
|
16
26
|
# Side load controller rendered ViewComponent
|
17
|
-
|
18
|
-
|
27
|
+
Importer.sideload "app/views/#{layout.virtual_path}" if layout
|
28
|
+
Importer.sideload "app/views/#{renderable.virtual_path}"
|
19
29
|
elsif template.respond_to?(:virtual_path) &&
|
20
30
|
template.respond_to?(:type) && template.type == :html
|
21
|
-
|
31
|
+
Importer.sideload "app/views/#{layout.virtual_path}" if layout
|
22
32
|
|
23
33
|
# Try side loading the variant template
|
24
34
|
if template.respond_to?(:variant) && template.variant
|
25
|
-
|
35
|
+
Importer.sideload "app/views/#{template.virtual_path}+#{template.variant}"
|
26
36
|
end
|
27
37
|
|
28
|
-
|
38
|
+
Importer.sideload "app/views/#{template.virtual_path}"
|
29
39
|
end
|
30
40
|
|
31
41
|
super
|
@@ -36,20 +46,14 @@ class Proscenium::SideLoad
|
|
36
46
|
private
|
37
47
|
|
38
48
|
def render_partial_template(view, locals, template, layout, block)
|
39
|
-
if template.respond_to?(:virtual_path) &&
|
49
|
+
if Proscenium.config.side_load && template.respond_to?(:virtual_path) &&
|
40
50
|
template.respond_to?(:type) && template.type == :html
|
41
|
-
|
42
|
-
|
51
|
+
Importer.sideload "app/views/#{layout.virtual_path}" if layout
|
52
|
+
Importer.sideload "app/views/#{template.virtual_path}"
|
43
53
|
end
|
44
54
|
|
45
55
|
super
|
46
56
|
end
|
47
|
-
|
48
|
-
def build_rendered_template(content, template)
|
49
|
-
path = Rails.root.join('app', 'views', template.virtual_path)
|
50
|
-
cssm = Proscenium::CssModule::Resolver.new(path)
|
51
|
-
super cssm.compile_class_names(content), template
|
52
|
-
end
|
53
57
|
end
|
54
58
|
end
|
55
59
|
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|