proscenium 0.19.0.beta6 → 0.19.0.beta7
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 +3 -26
- data/lib/proscenium/builder.rb +11 -35
- data/lib/proscenium/bundled_gems.rb +37 -0
- data/lib/proscenium/css_module/transformer.rb +1 -1
- data/lib/proscenium/ext/proscenium +0 -0
- data/lib/proscenium/ext/proscenium.h +1 -7
- data/lib/proscenium/helper.rb +3 -9
- data/lib/proscenium/importer.rb +13 -11
- data/lib/proscenium/log_subscriber.rb +0 -12
- data/lib/proscenium/middleware/base.rb +10 -8
- data/lib/proscenium/middleware/esbuild.rb +1 -6
- data/lib/proscenium/middleware/ruby_gems.rb +23 -0
- data/lib/proscenium/middleware.rb +26 -22
- data/lib/proscenium/monkey.rb +3 -5
- data/lib/proscenium/phlex/asset_inclusions.rb +0 -1
- data/lib/proscenium/railtie.rb +0 -27
- data/lib/proscenium/registry/bundled_package.rb +29 -0
- data/lib/proscenium/registry/package.rb +95 -0
- data/lib/proscenium/registry/ruby_gem_package.rb +28 -0
- data/lib/proscenium/registry.rb +29 -0
- data/lib/proscenium/resolver.rb +23 -18
- data/lib/proscenium/ruby_gems.rb +67 -0
- data/lib/proscenium/side_load.rb +20 -63
- data/lib/proscenium/ui/flash/bun.lock +19 -0
- data/lib/proscenium/ui/flash/index.js +6 -2
- data/lib/proscenium/ui/flash/node_modules/dom-mutations/index.d.ts +33 -0
- data/lib/proscenium/ui/flash/node_modules/dom-mutations/index.js +44 -0
- data/lib/proscenium/ui/flash/node_modules/dom-mutations/license +9 -0
- data/lib/proscenium/ui/flash/node_modules/dom-mutations/package.json +59 -0
- data/lib/proscenium/ui/flash/node_modules/dom-mutations/readme.md +125 -0
- data/lib/proscenium/ui/flash/node_modules/sourdough-toast/LICENSE +20 -0
- data/lib/proscenium/ui/flash/node_modules/sourdough-toast/README.md +11 -0
- data/lib/proscenium/ui/flash/node_modules/sourdough-toast/package.json +44 -0
- data/lib/proscenium/ui/flash/node_modules/sourdough-toast/src/sourdough-toast.css +697 -0
- data/lib/proscenium/ui/flash/node_modules/sourdough-toast/src/sourdough-toast.js +537 -0
- data/lib/proscenium/ui/flash/package.json +11 -0
- data/lib/proscenium/ui/react-manager/index.jsx +3 -22
- data/lib/proscenium/ui/ujs/index.js +1 -1
- data/lib/proscenium/version.rb +1 -1
- data/lib/proscenium.rb +3 -4
- metadata +21 -3
- data/lib/proscenium/middleware/engines.rb +0 -41
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e9c86c8fc9ea5069434688a114679c39ea03b431af308efbe0b65360f0b5b935
|
4
|
+
data.tar.gz: 4d74b86e4d0aed1651fdc369c4f830b1f85b9b33dad0d109905df3df9581dddc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c64c401d4ef4e70fedf4e6327177dc53645bcd540de6301f64edfb9020cf8686fe444377ea538ef9a09d2b0699229ab3a138f8704f006e89b5acfb0a39666355
|
7
|
+
data.tar.gz: 249ae5d9c8fe4884b2ad5c7ab2712665522007f75ced0c2d56b4f6ee189e868c0e58122a4f96aade3b24443037b3977356595fd3881dc4fd107151e28c3c751d
|
data/README.md
CHANGED
@@ -53,7 +53,6 @@
|
|
53
53
|
- [Cache Busting](#cache-busting)
|
54
54
|
- [rjs is back!](#rjs-is-back)
|
55
55
|
- [Resolution](#resolution)
|
56
|
-
- [Assets from Rails Engines](#assets-from-rails-engines)
|
57
56
|
- [Thanks](#thanks)
|
58
57
|
- [Development](#development)
|
59
58
|
|
@@ -253,15 +252,13 @@ import Header from "/app/components/header";
|
|
253
252
|
|
254
253
|
### Unbundling
|
255
254
|
|
256
|
-
Sometimes you don't want to bundle an import. For example, you want to ensure that only one instance of React is loaded. In
|
255
|
+
Sometimes you don't want to bundle an import. For example, you want to ensure that only one instance of React is loaded. In these cases, you can use the `unbundle` import attribute:
|
257
256
|
|
258
257
|
```js
|
259
|
-
import React from "unbundle:
|
258
|
+
import React from "react" with { unbundle: 'true' };
|
260
259
|
```
|
261
260
|
|
262
|
-
|
263
|
-
|
264
|
-
You can also use the `unbundle` prefix in your [import map](#import-maps), which ensures that all imports of a particular path is always unbundled:
|
261
|
+
You can also unbundle entries in your [import map](#import-maps) using an `unbundle:` prefix, which ensures that all imports of a particular path are always unbundled:
|
265
262
|
|
266
263
|
```json
|
267
264
|
{
|
@@ -851,26 +848,6 @@ You can continue to access any file in the `/public` directory as you normally w
|
|
851
848
|
|
852
849
|
If requesting a file that exists in a root directory and the public directory, the file in the public directory will be served. For example, if you have a file at `/lib/foo.js` and `/public/lib/foo.js`, and you request `/lib/foo.js`, the file in the public directory (`/public/lib/foo.js`) will be served.
|
853
850
|
|
854
|
-
### Assets from Rails Engines
|
855
|
-
|
856
|
-
Proscenium can serve assets from Rails Engines that are installed in your Rails app.
|
857
|
-
|
858
|
-
An engine that wants to expose its assets via Proscenium to the application must add Proscenium as a dependency, and add itself to the list of engines in the Proscenium config options `Proscenium.config.engines`.
|
859
|
-
|
860
|
-
For example, we have a gem called `gem1` that has Proscenium as a dependency, and exposes a Rails engine. It has some assets that it wants to expose to the application. To do this, it adds itself to the list of engines in the Proscenium config `engines` option:
|
861
|
-
|
862
|
-
```ruby
|
863
|
-
class Gem1::Engine < ::Rails::Engine
|
864
|
-
config.proscenium.engines << self
|
865
|
-
end
|
866
|
-
```
|
867
|
-
|
868
|
-
When this gem is installed in any Rails application, its assets will be available at the URL `/gem1/...`. For example, if the gem has a file `lib/styles.css`, it can be requested at `/gem1/lib/styles.css`.
|
869
|
-
|
870
|
-
The same directories and file extensions are supported as for the application itself.
|
871
|
-
|
872
|
-
It is important to note that the application takes precedence over the gem. So if the application has a file at `/public/gem1/lib/styles.css`, and the gem also has a file at `/lib/styles.css`, then the file in the application will be served. This is because both files would be accessible at the same URL: `/gem1/lib/styles.css`.
|
873
|
-
|
874
851
|
## Thanks
|
875
852
|
|
876
853
|
HUGE thanks 🙏 go to [Evan Wallace](https://github.com/evanw) and his amazing [esbuild](https://esbuild.github.io/) project. Proscenium would not be possible without it, and it is esbuild that makes this so fast and efficient.
|
data/lib/proscenium/builder.rb
CHANGED
@@ -10,7 +10,8 @@ module Proscenium
|
|
10
10
|
|
11
11
|
class Result < FFI::Struct
|
12
12
|
layout :success, :bool,
|
13
|
-
:response, :string
|
13
|
+
:response, :string,
|
14
|
+
:content_hash, :string
|
14
15
|
end
|
15
16
|
|
16
17
|
module Request
|
@@ -24,11 +25,6 @@ module Proscenium
|
|
24
25
|
:pointer # Config as JSON.
|
25
26
|
], Result.by_value
|
26
27
|
|
27
|
-
attach_function :build_to_path, [
|
28
|
-
:string, # Path or entry point. Multiple can be given by separating with a semi-colon
|
29
|
-
:pointer # Config as JSON.
|
30
|
-
], Result.by_value
|
31
|
-
|
32
28
|
attach_function :resolve, [
|
33
29
|
:string, # path or entry point
|
34
30
|
:pointer # Config as JSON.
|
@@ -44,6 +40,7 @@ module Proscenium
|
|
44
40
|
@error = JSON.parse(error, strict: true).deep_transform_keys(&:underscore)
|
45
41
|
|
46
42
|
msg = @error['text']
|
43
|
+
msg << ' - ' << @error['detail'] if @error['detail'].is_a?(String)
|
47
44
|
if (location = @error['location'])
|
48
45
|
msg << " at #{location['file']}:#{location['line']}:#{location['column']}"
|
49
46
|
end
|
@@ -60,12 +57,8 @@ module Proscenium
|
|
60
57
|
end
|
61
58
|
end
|
62
59
|
|
63
|
-
def self.
|
64
|
-
new(root:).
|
65
|
-
end
|
66
|
-
|
67
|
-
def self.build_to_string(path, root: nil, bundle: nil)
|
68
|
-
new(root:, bundle:).build_to_string(path)
|
60
|
+
def self.build_to_string(path, root: nil)
|
61
|
+
new(root:).build_to_string(path)
|
69
62
|
end
|
70
63
|
|
71
64
|
def self.resolve(path, root: nil)
|
@@ -77,43 +70,27 @@ module Proscenium
|
|
77
70
|
Request.reset_config
|
78
71
|
end
|
79
72
|
|
80
|
-
def initialize(root: nil
|
81
|
-
bundle = Proscenium.config.bundle if bundle.nil?
|
82
|
-
|
73
|
+
def initialize(root: nil)
|
83
74
|
@request_config = FFI::MemoryPointer.from_string({
|
84
75
|
RootPath: (root || Rails.root).to_s,
|
85
76
|
GemPath: gem_root,
|
86
77
|
Environment: ENVIRONMENTS.fetch(Rails.env.to_sym, 2),
|
87
|
-
Engines: Proscenium.config.engines,
|
88
78
|
EnvVars: env_vars,
|
89
79
|
CodeSplitting: Proscenium.config.code_splitting,
|
90
|
-
|
91
|
-
Bundle: bundle,
|
80
|
+
RubyGems: Proscenium::BundledGems.paths,
|
81
|
+
Bundle: Proscenium.config.bundle,
|
82
|
+
QueryString: cache_query_string,
|
92
83
|
Debug: Proscenium.config.debug
|
93
84
|
}.to_json)
|
94
85
|
end
|
95
86
|
|
96
|
-
def build_to_path(path)
|
97
|
-
ActiveSupport::Notifications.instrument('build_to_path.proscenium',
|
98
|
-
identifier: path,
|
99
|
-
cached: Proscenium.cache.exist?(path)) do
|
100
|
-
Proscenium.cache.fetch path do
|
101
|
-
result = Request.build_to_path(path, @request_config)
|
102
|
-
|
103
|
-
raise BuildError, result[:response] unless result[:success]
|
104
|
-
|
105
|
-
result[:response]
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
87
|
def build_to_string(path)
|
111
88
|
ActiveSupport::Notifications.instrument('build_to_string.proscenium', identifier: path) do
|
112
89
|
result = Request.build_to_string(path, @request_config)
|
113
90
|
|
114
91
|
raise BuildError, result[:response] unless result[:success]
|
115
92
|
|
116
|
-
result
|
93
|
+
result
|
117
94
|
end
|
118
95
|
end
|
119
96
|
|
@@ -137,8 +114,7 @@ module Proscenium
|
|
137
114
|
end
|
138
115
|
|
139
116
|
def cache_query_string
|
140
|
-
|
141
|
-
q ? "--cache-query-string #{q}" : nil
|
117
|
+
Proscenium.config.cache_query_string.presence || ''
|
142
118
|
end
|
143
119
|
|
144
120
|
def gem_root
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Proscenium
|
4
|
+
module BundledGems
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def paths
|
8
|
+
@paths ||= begin
|
9
|
+
specs = Bundler.load.specs.reject { |s| s.name == 'bundler' }.sort_by(&:name)
|
10
|
+
|
11
|
+
raise 'No gems in your Gemfile' if specs.empty?
|
12
|
+
|
13
|
+
bundle = {}
|
14
|
+
specs.each do |s|
|
15
|
+
bundle[s.name] = if s.name == 'proscenium'
|
16
|
+
Pathname(s.full_gem_path).join('lib/proscenium/ui').to_s
|
17
|
+
else
|
18
|
+
s.full_gem_path
|
19
|
+
end
|
20
|
+
end
|
21
|
+
bundle
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def pathname_for(name)
|
26
|
+
(path = paths[name]) ? Pathname(path) : nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def pathname_for!(name)
|
30
|
+
unless (path = pathname_for(name))
|
31
|
+
raise "Gem `#{name}` not found in your Gemfile"
|
32
|
+
end
|
33
|
+
|
34
|
+
path
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -70,7 +70,7 @@ module Proscenium
|
|
70
70
|
digest = Importer.import(resolved_path)
|
71
71
|
|
72
72
|
transformed_path = ''
|
73
|
-
transformed_path = "__#{resolved_path[1..].gsub(%r{[
|
73
|
+
transformed_path = "__#{resolved_path[1..].gsub(%r{[@/\.+]}, '-')}" if Rails.env.development?
|
74
74
|
transformed_name = name.to_s
|
75
75
|
transformed_name = if transformed_name.start_with?('_')
|
76
76
|
"_#{transformed_name[1..]}-#{digest}#{transformed_path}"
|
Binary file
|
@@ -24,6 +24,7 @@ typedef struct { const char *p; ptrdiff_t n; } _GoString_;
|
|
24
24
|
struct Result {
|
25
25
|
int success;
|
26
26
|
char* response;
|
27
|
+
char* contentHash;
|
27
28
|
};
|
28
29
|
|
29
30
|
#line 1 "cgo-generated-wrapper"
|
@@ -91,13 +92,6 @@ extern void reset_config();
|
|
91
92
|
//
|
92
93
|
extern struct Result build_to_string(char* filePath, char* configJson);
|
93
94
|
|
94
|
-
// Build the given `path` in the `root`.
|
95
|
-
//
|
96
|
-
// - path - The path to build relative to `root`.
|
97
|
-
// - config
|
98
|
-
//
|
99
|
-
extern struct Result build_to_path(char* filePath, char* configJson);
|
100
|
-
|
101
95
|
// Resolve the given `path` relative to the `root`.
|
102
96
|
//
|
103
97
|
// - path - The path to build relative to `root`.
|
data/lib/proscenium/helper.rb
CHANGED
@@ -21,10 +21,8 @@ module Proscenium
|
|
21
21
|
# /app/javascript.
|
22
22
|
def compute_asset_path(path, options = {})
|
23
23
|
if %i[javascript stylesheet].include?(options[:type])
|
24
|
-
path.prepend DEFAULT_RAILS_ASSET_PATHS[options[:type]]
|
25
|
-
|
26
|
-
result = Proscenium::Builder.build_to_path(path)
|
27
|
-
return result.split('::').last.delete_prefix 'public'
|
24
|
+
path.prepend DEFAULT_RAILS_ASSET_PATHS[options[:type]] if !path.start_with?('./', '../')
|
25
|
+
return path
|
28
26
|
end
|
29
27
|
|
30
28
|
super
|
@@ -61,16 +59,12 @@ module Proscenium
|
|
61
59
|
def include_stylesheets
|
62
60
|
SideLoad::CSS_COMMENT.html_safe
|
63
61
|
end
|
64
|
-
alias side_load_stylesheets include_stylesheets
|
65
|
-
deprecate side_load_stylesheets: 'Use `include_stylesheets` instead', deprecator: Deprecator.new
|
66
62
|
|
67
63
|
# Includes all javascripts that have been imported and side loaded.
|
68
64
|
#
|
69
65
|
# @return [String] the HTML tags for the javascripts.
|
70
66
|
def include_javascripts
|
71
|
-
|
67
|
+
SideLoad::JS_COMMENT.html_safe
|
72
68
|
end
|
73
|
-
alias side_load_javascripts include_javascripts
|
74
|
-
deprecate side_load_javascripts: 'Use `include_javascripts` instead', deprecator: Deprecator.new
|
75
69
|
end
|
76
70
|
end
|
data/lib/proscenium/importer.rb
CHANGED
@@ -23,12 +23,11 @@ module Proscenium
|
|
23
23
|
#
|
24
24
|
# @param filepath [String] Absolute URL path (relative to Rails root) of the file to import.
|
25
25
|
# Should be the actual asset file, eg. app.css, some/component.js.
|
26
|
-
# @
|
27
|
-
|
28
|
-
def import(filepath = nil, resolve: nil, **)
|
26
|
+
# @return [String|nil] the digest of the imported file path if a css module (*.module.css).
|
27
|
+
def import(filepath = nil, **)
|
29
28
|
self.imported ||= {}
|
30
29
|
|
31
|
-
filepath =
|
30
|
+
filepath = "/node_modules/#{filepath}" if filepath.start_with?('@rubygems/')
|
32
31
|
css_module = filepath.end_with?('.module.css')
|
33
32
|
|
34
33
|
unless self.imported.key?(filepath)
|
@@ -60,6 +59,7 @@ module Proscenium
|
|
60
59
|
# `.tsx` extension is matched first.
|
61
60
|
#
|
62
61
|
# @param filepath [Pathname] Absolute file system path of the Ruby file to sideload.
|
62
|
+
# @param options [Hash] Options to pass to `import`.
|
63
63
|
def sideload(filepath, **options)
|
64
64
|
return if !Proscenium.config.side_load || (options[:js] == false && options[:css] == false)
|
65
65
|
|
@@ -75,20 +75,22 @@ module Proscenium
|
|
75
75
|
_sideload(filepath, CSS_EXTENSIONS, **)
|
76
76
|
end
|
77
77
|
|
78
|
+
# @param filepath [Pathname] Absolute file system path of the Ruby file to sideload.
|
79
|
+
# @param extensions [Array<String>] Supported file extensions to sideload.
|
80
|
+
# @param options [Hash] Options to pass to `import`.
|
81
|
+
# @raise [ArgumentError] if `filepath` is not an absolute file system path.
|
78
82
|
private def _sideload(filepath, extensions, **options) # rubocop:disable Style/AccessModifierDeclarations
|
79
83
|
return unless Proscenium.config.side_load
|
80
84
|
|
81
|
-
filepath
|
85
|
+
if !filepath.is_a?(Pathname) || !filepath.absolute?
|
86
|
+
raise ArgumentError, "`filepath` (#{filepath}) must be a `Pathname`, and an absolute path"
|
87
|
+
end
|
88
|
+
|
82
89
|
filepath = filepath.sub_ext('')
|
83
90
|
|
84
91
|
extensions.find do |x|
|
85
92
|
if (fp = filepath.sub_ext(x)).exist?
|
86
|
-
|
87
|
-
fp.sub!(Proscenium.ui_path_regex, 'proscenium/')
|
88
|
-
import(Resolver.resolve(fp), sideloaded: true, **options)
|
89
|
-
else
|
90
|
-
import(Resolver.resolve(fp.to_s), sideloaded: true, **options)
|
91
|
-
end
|
93
|
+
import(Resolver.resolve(fp.to_s), sideloaded: true, **options)
|
92
94
|
end
|
93
95
|
end
|
94
96
|
end
|
@@ -10,18 +10,6 @@ module Proscenium
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
-
def build_to_path(event)
|
14
|
-
path = event.payload[:identifier]
|
15
|
-
cached = event.payload[:cached] ? ' | Cached!' : ''
|
16
|
-
path = CGI.unescape(path) if path.start_with?(/https?%3A%2F%2F/)
|
17
|
-
|
18
|
-
info do
|
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
13
|
def build_to_string(event)
|
26
14
|
path = event.payload[:identifier]
|
27
15
|
path = CGI.unescape(path) if path.start_with?(/https?%3A%2F%2F/)
|
@@ -77,24 +77,26 @@ module Proscenium
|
|
77
77
|
end
|
78
78
|
end
|
79
79
|
|
80
|
-
def render_response(
|
80
|
+
def render_response(result)
|
81
|
+
content = result[:response]
|
82
|
+
|
81
83
|
response = Rack::Response.new
|
82
|
-
response.write content
|
83
|
-
response.content_type = content_type
|
84
84
|
response['X-Proscenium-Middleware'] = name
|
85
85
|
response.set_header 'SourceMap', "#{@request.path_info}.map"
|
86
|
+
response.content_type = content_type
|
87
|
+
response.etag = result[:content_hash]
|
86
88
|
|
87
89
|
if Proscenium.config.cache_query_string && Proscenium.config.cache_max_age
|
88
90
|
response.cache! Proscenium.config.cache_max_age
|
89
91
|
end
|
90
92
|
|
91
|
-
|
92
|
-
|
93
|
-
response.
|
93
|
+
if @request.fresh?(response)
|
94
|
+
response.status = 304
|
95
|
+
response.body = []
|
96
|
+
else
|
97
|
+
response.write content
|
94
98
|
end
|
95
99
|
|
96
|
-
yield response if block_given?
|
97
|
-
|
98
100
|
response.finish
|
99
101
|
end
|
100
102
|
|
@@ -20,12 +20,7 @@ module Proscenium
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def attempt
|
23
|
-
|
24
|
-
if Proscenium.config.external_node_modules && path_to_build.start_with?('node_modules/')
|
25
|
-
bundle = false
|
26
|
-
end
|
27
|
-
|
28
|
-
render_response Builder.build_to_string(path_to_build, bundle:)
|
23
|
+
render_response Builder.build_to_string(path_to_build)
|
29
24
|
rescue Builder::CompileError => e
|
30
25
|
raise self.class::CompileError, { file: @request.fullpath, detail: e.message }, caller
|
31
26
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Proscenium
|
4
|
+
class Middleware
|
5
|
+
class RubyGems < Esbuild
|
6
|
+
def real_path
|
7
|
+
@real_path ||= Pathname.new(gem_request_path.delete_prefix("#{gem_name}/")).to_s
|
8
|
+
end
|
9
|
+
|
10
|
+
def root_for_readable
|
11
|
+
BundledGems.pathname_for!(gem_name)
|
12
|
+
end
|
13
|
+
|
14
|
+
def gem_name
|
15
|
+
@gem_name ||= gem_request_path.split('/').first
|
16
|
+
end
|
17
|
+
|
18
|
+
def gem_request_path
|
19
|
+
@gem_request_path ||= @request.path.delete_prefix('/node_modules/@rubygems/')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -9,7 +9,7 @@ module Proscenium
|
|
9
9
|
|
10
10
|
autoload :Base
|
11
11
|
autoload :Esbuild
|
12
|
-
autoload :
|
12
|
+
autoload :RubyGems
|
13
13
|
|
14
14
|
def initialize(app)
|
15
15
|
@app = app
|
@@ -20,10 +20,25 @@ module Proscenium
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def call(env)
|
23
|
-
request =
|
23
|
+
request = ActionDispatch::Request.new(env)
|
24
24
|
|
25
25
|
return @app.call(env) if !request.get? && !request.head?
|
26
|
-
|
26
|
+
|
27
|
+
if request.path.match?(%r{^/_asset_chunks/})
|
28
|
+
response = Rack::Response[*@chunk_handler.attempt(request.env)]
|
29
|
+
response.etag = request.path.match(/-\$([a-z0-9]+)\$/i)[1]
|
30
|
+
|
31
|
+
if Proscenium.config.cache_query_string && Proscenium.config.cache_max_age
|
32
|
+
response.cache! Proscenium.config.cache_max_age
|
33
|
+
end
|
34
|
+
|
35
|
+
if request.fresh?(response)
|
36
|
+
response.status = 304
|
37
|
+
response.body = []
|
38
|
+
end
|
39
|
+
|
40
|
+
return response.finish
|
41
|
+
end
|
27
42
|
|
28
43
|
attempt(request) || @app.call(env)
|
29
44
|
end
|
@@ -33,40 +48,29 @@ module Proscenium
|
|
33
48
|
def attempt(request)
|
34
49
|
return unless (type = find_type(request))
|
35
50
|
|
36
|
-
# file_handler.attempt(request.env) || type.attempt(request)
|
37
|
-
|
38
51
|
type.attempt request
|
39
52
|
end
|
40
53
|
|
41
54
|
def find_type(request)
|
42
|
-
return Esbuild if Pathname.new(request.path).fnmatch?(app_path_glob, File::FNM_EXTGLOB)
|
43
|
-
|
44
55
|
pathname = Pathname.new(request.path)
|
45
|
-
|
46
|
-
|
56
|
+
|
57
|
+
if pathname.fnmatch?(gems_path_glob, File::FNM_EXTGLOB)
|
58
|
+
RubyGems
|
59
|
+
elsif pathname.fnmatch?(app_path_glob, File::FNM_EXTGLOB)
|
60
|
+
Esbuild
|
61
|
+
end
|
47
62
|
end
|
48
63
|
|
49
64
|
def app_path_glob
|
50
65
|
"/{#{Proscenium::ALLOWED_DIRECTORIES}}/**.{#{file_extensions}}"
|
51
66
|
end
|
52
67
|
|
53
|
-
def
|
54
|
-
|
55
|
-
"/{#{names.join(',')}}/{#{Proscenium::ALLOWED_DIRECTORIES}}/**.{#{file_extensions}}"
|
56
|
-
end
|
57
|
-
|
58
|
-
def ui_path_glob
|
59
|
-
"/proscenium/**.{#{file_extensions}}"
|
68
|
+
def gems_path_glob
|
69
|
+
"/node_modules/@rubygems/**.{#{file_extensions}}"
|
60
70
|
end
|
61
71
|
|
62
72
|
def file_extensions
|
63
73
|
@file_extensions ||= FILE_EXTENSIONS.join(',')
|
64
74
|
end
|
65
|
-
|
66
|
-
# TODO: handle precompiled assets
|
67
|
-
# def file_handler
|
68
|
-
# ::ActionDispatch::FileHandler.new Rails.public_path.join('assets').to_s,
|
69
|
-
# headers: { 'X-Proscenium-Middleware' => 'precompiled' }
|
70
|
-
# end
|
71
75
|
end
|
72
76
|
end
|
data/lib/proscenium/monkey.rb
CHANGED
@@ -1,12 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Proscenium
|
4
|
-
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
5
4
|
module Monkey
|
6
5
|
module TemplateRenderer
|
7
6
|
private
|
8
7
|
|
9
|
-
def render_template(view, template, layout_name, locals)
|
8
|
+
def render_template(view, template, layout_name, locals)
|
10
9
|
result = super
|
11
10
|
return result if !view.controller || !Proscenium.config.side_load
|
12
11
|
|
@@ -48,7 +47,7 @@ module Proscenium
|
|
48
47
|
options[k] = controller.instance_eval(&options[k]) if options[k].is_a?(Proc)
|
49
48
|
end
|
50
49
|
|
51
|
-
Importer.sideload "app/views/#{tpl.virtual_path}", **options
|
50
|
+
Importer.sideload Rails.root.join("app/views/#{tpl.virtual_path}"), **options
|
52
51
|
end
|
53
52
|
end
|
54
53
|
|
@@ -87,9 +86,8 @@ module Proscenium
|
|
87
86
|
options[k] = controller.instance_eval(&options[k]) if options[k].is_a?(Proc)
|
88
87
|
end
|
89
88
|
|
90
|
-
Importer.sideload "app/views/#{tpl.virtual_path}", **options
|
89
|
+
Importer.sideload Rails.root.join("app/views/#{tpl.virtual_path}"), **options
|
91
90
|
end
|
92
91
|
end
|
93
92
|
end
|
94
|
-
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
95
93
|
end
|
data/lib/proscenium/railtie.rb
CHANGED
@@ -14,42 +14,15 @@ module Proscenium
|
|
14
14
|
config.proscenium.bundle = true
|
15
15
|
config.proscenium.side_load = true
|
16
16
|
config.proscenium.code_splitting = true
|
17
|
-
config.proscenium.external_node_modules = false
|
18
17
|
|
19
|
-
# Cache asset paths when building to path. Enabled by default in production.
|
20
|
-
# @see Proscenium::Builder#build_to_path
|
21
|
-
config.proscenium.cache = ActiveSupport::Cache::MemoryStore.new if Rails.env.production?
|
22
|
-
|
23
|
-
# TODO: implement!
|
24
18
|
config.proscenium.cache_query_string = Rails.env.production? && ENV.fetch('REVISION', nil)
|
25
19
|
config.proscenium.cache_max_age = 2_592_000 # 30 days
|
26
20
|
|
27
|
-
# A proc that will be given the path to build, and should return a boolean indicating whether to
|
28
|
-
# cache the response.
|
29
|
-
#
|
30
|
-
# Example:
|
31
|
-
# cache_middleware_response = ->(path) { path.start_with?('node_modules/') }
|
32
|
-
config.proscenium.cache_middleware_response = nil
|
33
|
-
|
34
21
|
# List of environment variable names that should be passed to the builder, which will then be
|
35
22
|
# passed to esbuild's `Define` option. Being explicit about which environment variables are
|
36
23
|
# defined means a faster build, as esbuild will have less to do.
|
37
24
|
config.proscenium.env_vars = Set.new
|
38
25
|
|
39
|
-
# Rails engines to expose and allow Proscenium to serve their assets.
|
40
|
-
#
|
41
|
-
# A Rails engine that has assets, can add Proscenium as a gem dependency, and then add itself
|
42
|
-
# to this list. Proscenium will then serve the engine's assets at the URL path beginning with
|
43
|
-
# the engine name.
|
44
|
-
#
|
45
|
-
# Example:
|
46
|
-
# class Gem1::Engine < ::Rails::Engine
|
47
|
-
# config.proscenium.engines[:gem1] = root
|
48
|
-
# end
|
49
|
-
config.proscenium.engines = {
|
50
|
-
proscenium: Proscenium.ui_path
|
51
|
-
}
|
52
|
-
|
53
26
|
config.action_dispatch.rescue_templates = {
|
54
27
|
'Proscenium::Builder::BuildError' => 'build_error'
|
55
28
|
}
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rubygems/package'
|
4
|
+
|
5
|
+
class Proscenium::Registry
|
6
|
+
class BundledPackage < Package
|
7
|
+
def version = @version ||= spec.version.to_s
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def package_json
|
12
|
+
@package_json ||= begin
|
13
|
+
unless (gem_path = Proscenium::BundledGems.pathname_for(gem_name))
|
14
|
+
raise PackageNotInstalledError, name
|
15
|
+
end
|
16
|
+
|
17
|
+
if (package_path = gem_path.join('package.json')).exist?
|
18
|
+
JSON.parse(package_path.read)
|
19
|
+
else
|
20
|
+
default_package_json
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def spec
|
26
|
+
@spec ||= Bundler.load.specs[gem_name].first
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|