proscenium 0.11.0.pre.6-arm64-darwin → 0.11.0.pre.8-arm64-darwin

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8e16c640f7e3f07a1a29698ba0da0788bc05b7da96f77b2dcb052c79cae0c2f6
4
- data.tar.gz: cb366a06cd0bed0d24eb846a882a320aca309b3cddf3829678e42b1e80d78f9a
3
+ metadata.gz: c1aeea10539d16ab58dac312b335df36e93a83d7c5a8295a10f219692a317126
4
+ data.tar.gz: 1d2e3e8a6dd355a16de09434b3cf73376d1573fe5166b902c33db798ed2443d3
5
5
  SHA512:
6
- metadata.gz: 5fe0935ec4a1231e8bc85d2b5598b32f0276bd9bfe15fe027bc0481996eb1e43f3bff6915186f5b1af866562b037add10c671a871e0f3bc22e7bb389645473a6
7
- data.tar.gz: dc36da2d9821fcd0590baa0c75e1ea2c0c24cf0c687581ab8f4e954e840f970310de303ce8edd0e2aa9848c0eb26ac9b61fbc73993bee2967cb1c1d48dfdac66
6
+ metadata.gz: 8a1b1653b22e4029f86b52bce05960ded6e824568c326a0e3d4fbf2aefe29d9b4538a38f18a13f88f8e16b5a35d7b271bccff4e26246bd2ce4f1cae60b77b41c
7
+ data.tar.gz: 10ac695efc7f02901ec81b06850f28ca9452d57e8fe1437a877314bfce58a830e14cb1dc0a477ea0f3b800d8ec2c6f2353c84ac9419c41576d556cfcf8a3a925
data/README.md CHANGED
@@ -757,20 +757,36 @@ Rails.application.config.proscenium.cache_max_age = 12.months.to_i
757
757
 
758
758
  Proscenium brings back RJS! Any path ending in .rjs will be served from your Rails app. This allows you to import server rendered javascript.
759
759
 
760
- ## Serving from Ruby Gem
760
+ ## Resolution
761
761
 
762
- *docs needed*
762
+ Proscenium will serve files ending with any of these extension: `js,mjs,ts,css,jsx,tsx` from the following directories, and their sub-directories of your Rails application's root: `/app`, `/lib`, `/config`, `/node_modules`, `/vendor`.
763
763
 
764
- ## Included Paths
764
+ So a file at `/app/views/users/index.js` will be served from `https://yourapp.com/app/views/users/index.js`.
765
765
 
766
- By default, Proscenium will serve files ending with any of these extension: `js,mjs,ts,css,jsx,tsx`, and only from `app/assets`, `config`, `app/views`, `lib` and `node_modules` directories.
766
+ You can continue to access any file in the `/public` directory as you normally would. Proscenium will not process files in the `/public` directory.
767
767
 
768
- However, you can customise these paths with the `include_path` config option...
768
+ 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.
769
+
770
+ ### Assets from Rails Engines
771
+
772
+ Proscenium can serve assets from Rails Engines that are installed in your Rails app.
773
+
774
+ 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`.
775
+
776
+ 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:
769
777
 
770
778
  ```ruby
771
- Rails.application.config.proscenium.include_paths << 'app/components'
779
+ class Gem1::Engine < ::Rails::Engine
780
+ config.proscenium.engines << self
781
+ end
772
782
  ```
773
783
 
784
+ 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`.
785
+
786
+ The same directories and file extensions are supported as for the application itself.
787
+
788
+ 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`.
789
+
774
790
  ## Thanks
775
791
 
776
792
  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.
@@ -29,6 +29,7 @@ module Proscenium
29
29
  :string, # Proscenium gem root
30
30
  :environment, # Rails environment as a Symbol
31
31
  :bool, # code splitting enabled?
32
+ :string, # engine names and paths as a JSON string
32
33
  :bool # debugging enabled?
33
34
  ], Result.by_value
34
35
 
@@ -45,12 +46,17 @@ module Proscenium
45
46
  end
46
47
 
47
48
  class BuildError < StandardError
48
- attr_reader :error, :path
49
+ attr_reader :error
49
50
 
50
- def initialize(path, error)
51
- error = Oj.load(error, mode: :strict).deep_transform_keys(&:underscore)
51
+ def initialize(error)
52
+ @error = Oj.load(error, mode: :strict).deep_transform_keys(&:underscore)
52
53
 
53
- super "Failed to build '#{path}' -- #{error['text']}"
54
+ msg = @error['text']
55
+ if (location = @error['location'])
56
+ msg << " at #{location['file']}:#{location['line']}:#{location['column']}"
57
+ end
58
+
59
+ super msg
54
60
  end
55
61
  end
56
62
 
@@ -82,9 +88,10 @@ module Proscenium
82
88
  Pathname.new(__dir__).join('..', '..').to_s,
83
89
  Rails.env.to_sym,
84
90
  Proscenium.config.code_splitting,
91
+ engines.to_json,
85
92
  Proscenium.config.debug)
86
93
 
87
- raise BuildError.new(path, result[:response]) unless result[:success]
94
+ raise BuildError, result[:response] unless result[:success]
88
95
 
89
96
  result[:response]
90
97
  end
@@ -116,6 +123,10 @@ module Proscenium
116
123
  q ? "--cache-query-string #{q}" : nil
117
124
  end
118
125
 
126
+ def engines
127
+ Proscenium.config.engines.to_h { |e| [e.engine_name, e.root.to_s] }
128
+ end
129
+
119
130
  def import_map
120
131
  return unless (path = Rails.root&.join('config'))
121
132
 
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* appRoot, char* gemPath, unsigned int env, GoUint8 codeSplitting, GoUint8 debug);
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
  //
@@ -35,7 +35,14 @@ module Proscenium
35
35
  alias side_load_stylesheets include_stylesheets
36
36
  deprecate side_load_stylesheets: 'Use `include_stylesheets` instead', deprecator: Deprecator.new
37
37
 
38
- def include_javascripts(**options) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
38
+ # Includes all javascripts that have been imported and side loaded.
39
+ #
40
+ # @param extract_lazy_scripts [Boolean] if true, any lazy scripts will be extracted using
41
+ # `content_for` to `:proscenium_lazy_scripts` for later use. Be sure to include this in your
42
+ # page with the `declare_lazy_scripts` helper, or simply
43
+ # `content_for :proscenium_lazy_scripts`.
44
+ # @return [String] the HTML tags for the javascripts.
45
+ def include_javascripts(extract_lazy_scripts: false, **options) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
39
46
  out = []
40
47
 
41
48
  if Rails.application.config.proscenium.code_splitting && Importer.multiple_js_imported?
@@ -66,9 +73,15 @@ module Proscenium
66
73
  end
67
74
  end
68
75
 
69
- out << javascript_tag("window.prosceniumLazyScripts = #{scripts.to_json}")
76
+ if extract_lazy_scripts
77
+ content_for :proscenium_lazy_scripts do
78
+ javascript_tag "window.prosceniumLazyScripts = #{scripts.to_json}"
79
+ end
80
+ else
81
+ out << javascript_tag("window.prosceniumLazyScripts = #{scripts.to_json}")
82
+ end
70
83
  else
71
- Importer.each_javascript(delete: true) do |path, _path_options|
84
+ Importer.each_javascript(delete: true) do |path, _|
72
85
  out << javascript_include_tag(path, extname: false, **options)
73
86
  end
74
87
  end
@@ -77,5 +90,9 @@ module Proscenium
77
90
  end
78
91
  alias side_load_javascripts include_javascripts
79
92
  deprecate side_load_javascripts: 'Use `include_javascripts` instead', deprecator: Deprecator.new
93
+
94
+ def declare_lazy_scripts
95
+ content_for :proscenium_lazy_scripts
96
+ end
80
97
  end
81
98
  end
@@ -21,7 +21,7 @@ module Proscenium
21
21
  class << self
22
22
  # Import the given `filepath`. This is idempotent - it will never include duplicates.
23
23
  #
24
- # @param filepath [String] Absolute path (relative to Rails root) of the file to import.
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
26
  # @param resolve [String] description of the file to resolve and import.
27
27
  # @return [String] the digest of the imported file path if a css module (*.module.css).
@@ -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,9 +20,9 @@ module Proscenium
20
20
  end
21
21
 
22
22
  def attempt
23
- render_response Proscenium::Builder.build(path_to_build, root: Rails.root.to_s,
24
- base_url: @request.base_url)
25
- 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
26
26
  raise self.class::CompileError, { file: @request.fullpath, detail: e.message }, caller
27
27
  end
28
28
  end
@@ -9,6 +9,7 @@ module Proscenium
9
9
 
10
10
  autoload :Base
11
11
  autoload :Esbuild
12
+ autoload :Engines
12
13
  autoload :Runtime
13
14
  autoload :Url
14
15
 
@@ -42,13 +43,19 @@ module Proscenium
42
43
  def find_type(request)
43
44
  return Url if request.path.match?(%r{^/https?%3A%2F%2F})
44
45
  return Runtime if request.path.match?(%r{^/@proscenium/})
46
+ return Esbuild if Pathname.new(request.path).fnmatch?(app_path_glob, File::FNM_EXTGLOB)
45
47
 
46
- Esbuild if Pathname.new(request.path).fnmatch?(path_glob, File::FNM_EXTGLOB)
48
+ Engines if Pathname.new(request.path).fnmatch?(engines_path_glob, File::FNM_EXTGLOB)
47
49
  end
48
50
 
49
- def path_glob
50
- paths = Rails.application.config.proscenium.include_paths.join(',')
51
- "/{#{paths}}/**.{#{FILE_EXTENSIONS.join(',')}}"
51
+ def app_path_glob
52
+ "/{#{Proscenium::ALLOWED_DIRECTORIES}}/**.{#{FILE_EXTENSIONS.join(',')}}"
53
+ end
54
+
55
+ def engines_path_glob
56
+ names = Proscenium.config.engines.map(&:engine_name)
57
+
58
+ "/{#{names.join(',')}}/{#{Proscenium::ALLOWED_DIRECTORIES}}/**.{#{FILE_EXTENSIONS.join(',')}}"
52
59
  end
53
60
 
54
61
  # TODO: handle precompiled assets
@@ -3,6 +3,14 @@
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
 
@@ -20,6 +20,7 @@ module Proscenium
20
20
  define_output_helper :include_stylesheets
21
21
  define_output_helper :side_load_javascripts # deprecated
22
22
  define_output_helper :include_javascripts
23
+ define_output_helper :declare_lazy_scripts
23
24
 
24
25
  module Sideload
25
26
  def before_template
@@ -19,7 +19,6 @@ module Proscenium
19
19
  config.proscenium.debug = false
20
20
  config.proscenium.side_load = true
21
21
  config.proscenium.code_splitting = true
22
- config.proscenium.include_paths = Set.new(APPLICATION_INCLUDE_PATHS)
23
22
 
24
23
  # TODO: implement!
25
24
  config.proscenium.cache_query_string = Rails.env.production? && ENV.fetch('REVISION', nil)
@@ -30,36 +29,29 @@ module Proscenium
30
29
  # defined means a faster build, as esbuild will have less to do.
31
30
  config.proscenium.env_vars = Set.new
32
31
 
33
- # A hash of gems that can be side loaded. Assets from gems listed here can be side loaded.
32
+ # Rails engines to expose and allow Proscenium to serve their assets.
34
33
  #
35
- # Because side loading uses URL paths, any gem dependencies that side load assets will fail,
36
- # because the URL path will be relative to the application's root, and not the gem's root. By
37
- # specifying a list of gems that can be side loaded, Proscenium will be able to resolve the URL
38
- # path to the gem's root, and side load the asset.
39
- #
40
- # Side loading gems rely on NPM and a package.json file in the gem root. This ensures that any
41
- # dependencies are resolved correctly. This is required even if your gem has no package
42
- # dependencies.
34
+ # A Rails engine that has assets, can add Proscenium as a gem dependency, and then add itself
35
+ # to this list. Proscenium will then serve the engine's assets at the URL path beginning with
36
+ # the engine name.
43
37
  #
44
38
  # Example:
45
- # config.proscenium.side_load_gems['mygem'] = {
46
- # root: gem_root,
47
- # package_name: 'mygem'
48
- # }
49
- config.proscenium.side_load_gems = {}
39
+ # class Gem1::Engine < ::Rails::Engine
40
+ # config.proscenium.engines << self
41
+ # end
42
+ config.proscenium.engines = Set.new
50
43
 
51
- initializer 'proscenium.configuration' do |app|
52
- options = app.config.proscenium
53
- options.include_paths = Set.new(APPLICATION_INCLUDE_PATHS) if options.include_paths.blank?
54
- end
44
+ config.action_dispatch.rescue_templates = {
45
+ 'Proscenium::Builder::BuildError' => 'build_error'
46
+ }
55
47
 
56
48
  initializer 'proscenium.middleware' do |app|
57
49
  app.middleware.insert_after ActionDispatch::Static, Middleware
58
- app.middleware.insert_after ActionDispatch::Static, Rack::ETag, 'no-cache'
59
- app.middleware.insert_after ActionDispatch::Static, Rack::ConditionalGet
50
+ # app.middleware.insert_after ActionDispatch::Static, Rack::ETag, 'no-cache'
51
+ # app.middleware.insert_after ActionDispatch::Static, Rack::ConditionalGet
60
52
  end
61
53
 
62
- initializer 'proscenium.monkey_views' do
54
+ initializer 'proscenium.monkey_patches' do
63
55
  ActiveSupport.on_load(:action_view) do
64
56
  ActionView::TemplateRenderer.prepend Monkey::TemplateRenderer
65
57
  ActionView::PartialRenderer.prepend Monkey::PartialRenderer
@@ -77,3 +69,13 @@ module Proscenium
77
69
  end
78
70
  end
79
71
  end
72
+
73
+ # Monkey path ActionDispatch::DebugView to use our custom error template on BuildError exceptions.
74
+ class ActionDispatch::DebugView
75
+ def initialize(assigns)
76
+ paths = [RESCUES_TEMPLATE_PATH,
77
+ Proscenium::Railtie.root.join('lib', 'proscenium', 'templates').to_s]
78
+ lookup_context = ActionView::LookupContext.new(paths)
79
+ super(lookup_context, assigns, nil)
80
+ end
81
+ end
@@ -11,7 +11,9 @@ module Proscenium
11
11
  #
12
12
  # @param path [String] Can be URL path, file system path, or bare specifier (ie. NPM package).
13
13
  # @return [String] URL path.
14
- def self.resolve(path) # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
14
+ #
15
+ # rubocop:disable Metrics/*
16
+ def self.resolve(path)
15
17
  self.resolved ||= {}
16
18
 
17
19
  self.resolved[path] ||= begin
@@ -21,15 +23,8 @@ module Proscenium
21
23
 
22
24
  if path.start_with?('@proscenium/')
23
25
  "/#{path}"
24
- elsif (gem = Proscenium.config.side_load_gems.find do |_, x|
25
- path.start_with? "#{x[:root]}/"
26
- end)
27
- unless (package_name = gem[1][:package_name] || gem[0])
28
- # TODO: manually resolve the path without esbuild
29
- raise PathResolutionFailed, path
30
- end
31
-
32
- Builder.resolve "#{package_name}/#{path.delete_prefix("#{gem[1][:root]}/")}"
26
+ elsif (engine = Proscenium.config.engines.find { |e| path.start_with? "#{e.root}/" })
27
+ path.sub(/^#{engine.root}/, "/#{engine.engine_name}")
33
28
  elsif path.start_with?("#{Rails.root}/")
34
29
  path.delete_prefix Rails.root.to_s
35
30
  else
@@ -37,5 +32,6 @@ module Proscenium
37
32
  end
38
33
  end
39
34
  end
35
+ # rubocop:enable Metrics/*
40
36
  end
41
37
  end
@@ -0,0 +1,27 @@
1
+ <header>
2
+ <h1>
3
+ <%= @exception.class.to_s %>
4
+ </h1>
5
+ </header>
6
+
7
+ <main role="main" id="container">
8
+ <%= render "rescues/message_and_suggestions", exception: @exception %>
9
+
10
+ <% if @exception.error['location'] %>
11
+ <div class="source">
12
+ <div class="data">
13
+ <pre>
14
+
15
+ <%= @exception.error['location']['file'] %>:<%= @exception.error['location']['line'] %>:<%= @exception.error['location']['column'] %>
16
+
17
+ <%= @exception.error['location']['line'].to_s.rjust 5 %> │ <%= @exception.error['location']['line_text'] %>
18
+ │ <%= (@exception.error['location']['length'] > 1 ? "~" * @exception.error['location']['length'] : "^").rjust(@exception.error['location']['column'] + @exception.error['location']['length']) %>
19
+ <%- if @exception.error['location']['suggestion'].present? -%> + │ <%= @exception.error['location']['suggestion'].rjust(@exception.error['location']['column'] + 1) %>
20
+ <% else %> <%- end -%>
21
+ </pre>
22
+ </div>
23
+ </div>
24
+ <% end %>
25
+
26
+ <%= render template: "rescues/_request_and_response" %>
27
+ </main>
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Proscenium
4
- VERSION = '0.11.0.pre.6'
4
+ VERSION = '0.11.0.pre.8'
5
5
  end
data/lib/proscenium.rb CHANGED
@@ -8,8 +8,7 @@ module Proscenium
8
8
  FILE_EXTENSIONS = ['js', 'mjs', 'ts', 'jsx', 'tsx', 'css', 'js.map', 'mjs.map', 'jsx.map',
9
9
  'ts.map', 'tsx.map', 'css.map'].freeze
10
10
 
11
- APPLICATION_INCLUDE_PATHS = ['config', 'app/assets', 'app/views', 'app/components', 'lib',
12
- 'node_modules'].freeze
11
+ ALLOWED_DIRECTORIES = 'app,lib,config,vendor,node_modules'
13
12
 
14
13
  # Environment variables that should always be passed to the builder.
15
14
  DEFAULT_ENV_VARS = Set['RAILS_ENV', 'NODE_ENV'].freeze
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: proscenium
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.0.pre.6
4
+ version: 0.11.0.pre.8
5
5
  platform: arm64-darwin
6
6
  authors:
7
7
  - Joel Moss
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-09-14 00:00:00.000000000 Z
11
+ date: 2023-09-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -105,6 +105,7 @@ files:
105
105
  - lib/proscenium/log_subscriber.rb
106
106
  - lib/proscenium/middleware.rb
107
107
  - lib/proscenium/middleware/base.rb
108
+ - lib/proscenium/middleware/engines.rb
108
109
  - lib/proscenium/middleware/esbuild.rb
109
110
  - lib/proscenium/middleware/runtime.rb
110
111
  - lib/proscenium/middleware/url.rb
@@ -118,6 +119,7 @@ files:
118
119
  - lib/proscenium/resolver.rb
119
120
  - lib/proscenium/side_load.rb
120
121
  - lib/proscenium/source_path.rb
122
+ - lib/proscenium/templates/rescues/build_error.html.erb
121
123
  - lib/proscenium/utils.rb
122
124
  - lib/proscenium/version.rb
123
125
  - lib/proscenium/view_component.rb