proscenium 0.22.0.beta2 → 0.22.0.beta4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 707375c9a539b28a3aaf9e1001dc1a2a5071ba6c9af629fa8fe817cb7b0aa75b
4
- data.tar.gz: 48fce338315f5469c94659b211d38d498483da84ce670730a1ff5fe0eba0f983
3
+ metadata.gz: 9d0c8dd7c274032064d5c0b93eb8453f25b7cbdac3ad8bec9564b9c861d93310
4
+ data.tar.gz: bbc9259f705192b00a5882a9e0870555f34c10873668c408cbb60e02346d762e
5
5
  SHA512:
6
- metadata.gz: 54107e9670be466a97e09a520299ce25df4c390b848b1dc9bada33da3178596f37fc8701ae725007bb33422e838a00b168dfe2e16365b4096a66c78f59fe9579
7
- data.tar.gz: 119edaf379fa4b556427cb0456dc9391152d0435af16f0d4865dd0ce6954369268d38c63d97fefcb68addbd95b9fc1075b8118320b6052258e96c98f477e8d5a
6
+ metadata.gz: 5439a6f64fd7dbb342e4d0aab91032250e7431391519a791abfda0b7e3270b06f7875b8f0d4521e575da6982c2889ce93407887e5325af440574db7652064fbf
7
+ data.tar.gz: f018dec7e935e9347267a62e43a0c7ae33da6194f9b416a6954ec6f17f28938df28d07d7f7ff59709950d9dc4b668e3c2d4dd80de281e4415bb7068070369aaf
data/README.md CHANGED
@@ -18,7 +18,6 @@
18
18
  - Deep integration with Rails.
19
19
  - Automatically side-load JS and CSS for your layouts, views, and partials.
20
20
  - Import from NPM, URL's, and locally.
21
- - Server-side import map support.
22
21
  - CSS Modules & mixins.
23
22
  - Source maps.
24
23
 
@@ -30,7 +29,6 @@
30
29
  - [Side Loading](#side-loading)
31
30
  - [Importing](#importing-assets)
32
31
  - [Local Imports](#local-imports)
33
- - [Import Maps](#import-maps)
34
32
  - [Source Maps](#source-maps)
35
33
  - [SVG](#svg)
36
34
  - [Environment Variables](#environment-variables)
@@ -48,9 +46,10 @@
48
46
  - [Typescript Caveats](#typescript-caveats)
49
47
  - [JSX](#jsx)
50
48
  - [JSON](#json)
51
- - [Cache Busting](#cache-busting)
52
49
  - [rjs is back!](#rjs-is-back)
53
50
  - [Resolution](#resolution)
51
+ - [Aliases](#aliases)
52
+ - [Pre-compilation](#precompilation)
54
53
  - [Thanks](#thanks)
55
54
  - [Development](#development)
56
55
 
@@ -258,13 +257,11 @@ Sometimes you don't want to bundle an import. For example, you want to ensure th
258
257
  import React from "react" with { unbundle: 'true' };
259
258
  ```
260
259
 
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:
260
+ You can also unbundle entries in [`aliases`](#aliases) using an `unbundle:` prefix, which ensures that all imports of a particular path are always unbundled:
262
261
 
263
- ```json
264
- {
265
- "imports": {
266
- "react": "unbundle:react"
267
- }
262
+ ```ruby
263
+ config.proscenium.aliases = {
264
+ "react": "unbundle:react"
268
265
  }
269
266
  ```
270
267
 
@@ -282,55 +279,6 @@ config.proscenium.bundle = false
282
279
 
283
280
  This will mean every asset and import will be loaded independently.
284
281
 
285
- ## Import Maps
286
-
287
- > **[WIP]**
288
-
289
- [Import maps](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap) for both JS and CSS is supported out of the box, and works with no regard to the browser being used. This is because the import map is parsed and resolved by Proscenium on the server, instead of by the browser. This is faster, and also allows you to use import maps in browsers that do not support them yet.
290
-
291
- If you are not familiar with import maps, think of them as a way to define aliases.
292
-
293
- Just create `config/import_map.json` and specify the imports you want to use. For example:
294
-
295
- ```json
296
- {
297
- "imports": {
298
- "react": "https://esm.sh/react@18.2.0",
299
- "start": "/lib/start.js",
300
- "common": "/lib/common.css",
301
- "@radix-ui/colors/": "https://esm.sh/@radix-ui/colors@0.1.8/"
302
- }
303
- }
304
- ```
305
-
306
- Using the above import map, we can do...
307
-
308
- ```js
309
- import { useCallback } from "react";
310
- import startHere from "start";
311
- import styles from "common";
312
- ```
313
-
314
- and for CSS...
315
-
316
- ```css
317
- @import "common";
318
- @import "@radix-ui/colors/blue.css";
319
- ```
320
-
321
- You can also write your import map in JavaScript instead of JSON. So instead of `config/import_map.json`, create `config/import_map.js`, and define an anonymous function. This function accepts a single `environment` argument.
322
-
323
- ```js
324
- (env) => ({
325
- imports: {
326
- react:
327
- env === "development"
328
- ? "https://esm.sh/react@18.2.0?dev"
329
- : "https://esm.sh/react@18.2.0",
330
- },
331
- });
332
- ```
333
-
334
282
  ## Source Maps
335
283
 
336
284
  Source maps can make it easier to debug your code. They encode the information necessary to translate from a line/column offset in a generated output file back to a line/column offset in the corresponding original input file. This is useful if your generated code is sufficiently different from your original code (e.g. your original code is TypeScript or you enabled minification). This is also useful if you prefer looking at individual files in your browser's developer tools instead of one big bundled file.
@@ -692,39 +640,59 @@ import { version } from "./package.json";
692
640
  console.log(version);
693
641
  ```
694
642
 
695
- ## Cache Busting
643
+ ## rjs is back
696
644
 
697
- By default, all assets are not cached by the browser. But if in production, you populate the `REVISION` env variable, all CSS and JS URL's will be appended with its value as a query string, and the `Cache-Control` response header will be set to `public` and a max-age of 30 days.
645
+ Proscenium brings back RJS! Any path ending in .rjs will be served from your Rails app. This allows you to import server rendered javascript.
698
646
 
699
- For example, if you set `REVISION=v1`, URL's will be appended with `?v1`: `/my/imported/file.js?v1`.
647
+ ## Resolution
700
648
 
701
- It is assumed that the `REVISION` env var will be unique between deploys. If it isn't, then assets will continue to be cached as the same version between deploys. I recommend you assign a version number or to use the Git commit hash of the deploy. Just make sure it is unique for each deploy.
649
+ 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`.
702
650
 
703
- You can set the `cache_query_string` config option directly to define any query string you wish:
651
+ So a file at `/app/views/users/index.js` will be served from `https://localhost:3000/app/views/users/index.js`.
704
652
 
705
- ```ruby
706
- Rails.application.config.proscenium.cache_query_string = 'my-cache-busting-version-string'
707
- ```
653
+ You can continue to access any file in the `/public` directory as you normally would. Proscenium will not process files in the `/public` directory.
654
+
655
+ 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.
656
+
657
+ ## Aliases
708
658
 
709
- The cache is set with a `max-age` of 30 days. You can customise this with the `cache_max_age` config option:
659
+ You can define import aliases via the `config.proscenium.aliases` config option. This allows you to create shorter or more meaningful import paths.
710
660
 
711
661
  ```ruby
712
- Rails.application.config.proscenium.cache_max_age = 12.months.to_i
662
+ config.proscenium.aliases = {
663
+ "utils": "/lib/utils.js",
664
+ "components": "/app/components"
665
+ }
713
666
  ```
714
667
 
715
- ## rjs is back
668
+ You can then import using the alias:
716
669
 
717
- Proscenium brings back RJS! Any path ending in .rjs will be served from your Rails app. This allows you to import server rendered javascript.
670
+ ```js
671
+ import utils from "utils";
672
+ import Header from "components/header";
673
+ ```
718
674
 
719
- ## Resolution
675
+ ## Pre-compilation
720
676
 
721
- 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`.
677
+ Proscenium is designed to bundle and minify your frontend code in real time, on demand, with no build step or pre-compilation needed. However, if you want to pre-compile your assets for production deployment, you can do so using the `assets:precompile` Rake task.
722
678
 
723
- So a file at `/app/views/users/index.js` will be served from `https://localhost:3000/app/views/users/index.js`.
679
+ ```bash
680
+ rails assets:precompile
681
+ ```
724
682
 
725
- You can continue to access any file in the `/public` directory as you normally would. Proscenium will not process files in the `/public` directory.
683
+ Be sure to specify a `Set` of paths which you want to pre-compile via the `config.proscenium.precompile` configuration option. Each path should be a glob pattern that matches the files which are your entry points. Don't include paths that are not entry points. For example:
726
684
 
727
- 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.
685
+ ```ruby
686
+ Rails.configuration.proscenium.precompile = Set[
687
+ "./app/components/**/*.js",
688
+ "./app/components/**/*.jsx",
689
+ "./app/views/**/*.js",
690
+ "./app/views/**/*.css",
691
+ "./app/views/**/*.module.css"
692
+ ]
693
+ ```
694
+
695
+ This will bundle, code split, tree shake, and compile all your JS, TS, JSX, TSX and CSS files and place them in the `public/assets` directory, ready to be served in production.
728
696
 
729
697
  ## Thanks
730
698
 
@@ -26,7 +26,6 @@ module Proscenium
26
26
 
27
27
  attach_function :build_to_string, [
28
28
  :string, # Path or entry point.
29
- :string, # cache_query_string.
30
29
  :pointer # Config as JSON.
31
30
  ], Result.by_value
32
31
 
@@ -68,8 +67,8 @@ module Proscenium
68
67
  end
69
68
  end
70
69
 
71
- def self.build_to_string(path, cache_query_string: '', root: nil)
72
- new(root:).build_to_string(path, cache_query_string:)
70
+ def self.build_to_string(path, root: nil)
71
+ new(root:).build_to_string(path)
73
72
  end
74
73
 
75
74
  def self.resolve(path, root: nil)
@@ -96,15 +95,15 @@ module Proscenium
96
95
  RubyGems: Proscenium::BundledGems.paths,
97
96
  Bundle: Proscenium.config.bundle,
98
97
  Aliases: Proscenium.config.aliases,
98
+ External: Proscenium.config.external,
99
99
  Precompile: Proscenium.config.precompile,
100
- QueryString: Proscenium.config.cache_query_string.presence || '',
101
100
  Debug: Proscenium.config.debug
102
101
  }.to_json)
103
102
  end
104
103
 
105
- def build_to_string(path, cache_query_string: '')
104
+ def build_to_string(path)
106
105
  ActiveSupport::Notifications.instrument('build.proscenium', identifier: path) do
107
- result = Request.build_to_string(path, cache_query_string, @request_config)
106
+ result = Request.build_to_string(path, @request_config)
108
107
 
109
108
  raise BuildError.new(path, result[:response]) unless result[:success]
110
109
 
Binary file
@@ -92,10 +92,9 @@ extern void reset_config();
92
92
  // Build the given `path` using the `config`.
93
93
  //
94
94
  // - path - The path to build relative to `root`.
95
- // - cache_query_string - The current query string used for cache busting, taken from the URL.
96
95
  // - config
97
96
  //
98
- extern struct Result build_to_string(char* filePath, char* cacheQueryString, char* configJson);
97
+ extern struct Result build_to_string(char* filePath, char* configJson);
99
98
 
100
99
  // Resolve the given `path` relative to the `root`.
101
100
  //
@@ -28,17 +28,6 @@ module Proscenium
28
28
  @path_to_build ||= @request.path[1..]
29
29
  end
30
30
 
31
- def cache_query_string
32
- @cache_query_string ||= begin
33
- params = @request.query_parameters
34
- if params.one? && params.first[0] != '' && params.first[1].nil?
35
- params.keys.first
36
- else
37
- Proscenium.config.cache_query_string
38
- end
39
- end.presence || ''
40
- end
41
-
42
31
  def sourcemap?
43
32
  @request.path.ends_with?('.map')
44
33
  end
@@ -85,10 +74,6 @@ module Proscenium
85
74
  response.content_type = content_type
86
75
  response.etag = result[:content_hash]
87
76
 
88
- if !cache_query_string.blank? && Proscenium.config.cache_max_age
89
- response.cache! Proscenium.config.cache_max_age
90
- end
91
-
92
77
  if @request.fresh?(response)
93
78
  response.status = 304
94
79
  response.body = []
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium
4
+ class Middleware
5
+ class Chunks
6
+ def initialize(app)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ request = ActionDispatch::Request.new(env)
12
+
13
+ return @app.call(env) unless request.path.match?(CHUNKS_PATH)
14
+
15
+ ActionDispatch::FileHandler.new(
16
+ Proscenium.config.output_path.to_s,
17
+ headers: {
18
+ 'X-Proscenium-Middleware' => 'chunks',
19
+ 'Cache-Control' => "public, max-age=#{100.years}, immutable",
20
+ 'ETag' => request.path.match(/-\$([a-z0-9]+)\$/i)[1]
21
+ }
22
+ ).attempt(request.env) || @app.call(env)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -4,7 +4,7 @@ module Proscenium
4
4
  class Middleware
5
5
  class Esbuild < Base
6
6
  def attempt
7
- render_response Builder.build_to_string(path_to_build, cache_query_string:)
7
+ render_response Builder.build_to_string(path_to_build)
8
8
  end
9
9
  end
10
10
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium
4
+ class Middleware
5
+ class Vendor
6
+ def initialize(app)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ request = ActionDispatch::Request.new(env)
12
+ pathname = Pathname.new(request.path)
13
+
14
+ return @app.call(env) unless pathname.fnmatch?(VENDOR_PATH_GLOB, File::FNM_EXTGLOB)
15
+
16
+ request.path_info = request.path.delete_prefix('/vendor')
17
+
18
+ ActionDispatch::FileHandler.new(
19
+ Rails.root.join('vendor').to_s,
20
+ headers: {
21
+ 'X-Proscenium-Middleware' => 'vendor',
22
+ 'Cache-Control' => "public, max-age=#{100.years}, immutable"
23
+ }
24
+ ).attempt(request.env) || @app.call(env)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -9,6 +9,8 @@ module Proscenium
9
9
  autoload :Base
10
10
  autoload :Esbuild
11
11
  autoload :RubyGems
12
+ autoload :Vendor
13
+ autoload :Chunks
12
14
  autoload :SilenceRequest
13
15
 
14
16
  def initialize(app)
@@ -20,19 +22,7 @@ module Proscenium
20
22
 
21
23
  return @app.call(env) if !request.get? && !request.head?
22
24
 
23
- # If this is a request for an asset chunk, we want to serve it with a very long
24
- # cache lifetime, since these are content-hashed and will never change.
25
- if request.path.match?(CHUNKS_PATH)
26
- ::ActionDispatch::FileHandler.new(
27
- Proscenium.config.output_path.to_s,
28
- headers: {
29
- 'Cache-Control' => "public, max-age=#{100.years}, immutable",
30
- 'etag' => request.path.match(/-\$([a-z0-9]+)\$/i)[1]
31
- }
32
- ).attempt(env) || @app.call(env)
33
- else
34
- attempt(request) || @app.call(env)
35
- end
25
+ attempt(request) || @app.call(env)
36
26
  end
37
27
 
38
28
  private
@@ -40,7 +30,7 @@ module Proscenium
40
30
  def attempt(request)
41
31
  return unless (type = find_type(request))
42
32
 
43
- type.attempt request
33
+ type.attempt(request)
44
34
  end
45
35
 
46
36
  def find_type(request)
@@ -15,9 +15,8 @@ module Proscenium
15
15
  config.proscenium.side_load = true
16
16
  config.proscenium.code_splitting = true
17
17
  config.proscenium.ensure_loaded = :raise
18
- config.proscenium.cache_query_string = Rails.env.production? && ENV.fetch('REVISION', nil)
19
- config.proscenium.cache_max_age = 2_592_000 # 30 days
20
18
  config.proscenium.aliases = {}
19
+ config.proscenium.external = Set['*.rjs', '*.gif', '*.jpg', '*.png', '*.woff2', '*.woff']
21
20
  config.proscenium.precompile = Set.new
22
21
  config.proscenium.output_dir = '/assets'
23
22
 
@@ -56,7 +55,18 @@ module Proscenium
56
55
  unless config.proscenium.logging
57
56
  app.middleware.insert_before Rails::Rack::Logger, Proscenium::Middleware::SilenceRequest
58
57
  end
59
- app.middleware.insert_before ActionDispatch::Callbacks, Proscenium::Middleware
58
+
59
+ app.middleware.insert_after Rack::Sendfile, Proscenium::Middleware::Vendor
60
+ app.middleware.insert_after Rack::Sendfile, Proscenium::Middleware::Chunks
61
+
62
+ # Ensure the middleware is inserted as early as possible.
63
+ if app.config.consider_all_requests_local
64
+ app.middleware.insert_before ActionDispatch::ActionableExceptions, Proscenium::Middleware
65
+ elsif app.config.reloading_enabled?
66
+ app.middleware.insert_before ActionDispatch::Reloader, Proscenium::Middleware
67
+ else
68
+ app.middleware.insert_before ActionDispatch::Callbacks, Proscenium::Middleware
69
+ end
60
70
  end
61
71
 
62
72
  initializer 'proscenium.sideloading' do
@@ -40,9 +40,6 @@ module Proscenium
40
40
  opts[:preload_links_header] = false if fragments
41
41
  opts[:data] ||= {}
42
42
 
43
- if Proscenium.config.cache_query_string.present?
44
- path += "?#{Proscenium.config.cache_query_string}"
45
- end
46
43
  out << helpers.stylesheet_link_tag(path.delete_prefix('/'), extname: false, **opts)
47
44
  end
48
45
 
@@ -71,9 +68,6 @@ module Proscenium
71
68
  opts = opts[:js].is_a?(Hash) ? opts[:js] : {}
72
69
  opts[:preload_links_header] = false if fragments
73
70
 
74
- if Proscenium.config.cache_query_string.present?
75
- path += "?#{Proscenium.config.cache_query_string}"
76
- end
77
71
  out << helpers.javascript_include_tag(path.delete_prefix('/'), extname: false, **opts)
78
72
  end
79
73
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Proscenium
4
- VERSION = '0.22.0.beta2'
4
+ VERSION = '0.22.0.beta4'
5
5
  end
data/lib/proscenium.rb CHANGED
@@ -14,7 +14,8 @@ module Proscenium
14
14
 
15
15
  FILE_EXTENSIONS = ['js', 'mjs', 'ts', 'jsx', 'tsx', 'css', 'js.map', 'mjs.map', 'jsx.map',
16
16
  'ts.map', 'tsx.map', 'css.map'].freeze
17
- ALLOWED_DIRECTORIES = 'app,lib,config,vendor,node_modules'
17
+ ALLOWED_DIRECTORIES = 'app,lib,config,node_modules'
18
+ VENDOR_PATH_GLOB = '/vendor/**.{js,css}'
18
19
  APP_PATH_GLOB = "/{#{ALLOWED_DIRECTORIES}}/**.{#{FILE_EXTENSIONS.join(',')}}".freeze
19
20
  GEMS_PATH_GLOB = "/node_modules/@rubygems/**.{#{FILE_EXTENSIONS.join(',')}}".freeze
20
21
  CHUNKS_PATH = %r{^/_asset_chunks/}
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.22.0.beta2
4
+ version: 0.22.0.beta4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel Moss
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-10-31 00:00:00.000000000 Z
11
+ date: 2025-11-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi
@@ -69,9 +69,11 @@ files:
69
69
  - lib/proscenium/manifest.rb
70
70
  - lib/proscenium/middleware.rb
71
71
  - lib/proscenium/middleware/base.rb
72
+ - lib/proscenium/middleware/chunks.rb
72
73
  - lib/proscenium/middleware/esbuild.rb
73
74
  - lib/proscenium/middleware/ruby_gems.rb
74
75
  - lib/proscenium/middleware/silence_request.rb
76
+ - lib/proscenium/middleware/vendor.rb
75
77
  - lib/proscenium/monkey.rb
76
78
  - lib/proscenium/railtie.rb
77
79
  - lib/proscenium/railties/assets.rake