proscenium 0.21.6-arm64-darwin → 0.22.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 +43 -75
- data/lib/proscenium/builder.rb +41 -22
- data/lib/proscenium/css_module.rb +1 -1
- data/lib/proscenium/ensure_loaded.rb +1 -1
- data/lib/proscenium/ext/proscenium +0 -0
- data/lib/proscenium/ext/proscenium.h +11 -1
- data/lib/proscenium/helper.rb +1 -10
- data/lib/proscenium/manifest.rb +40 -0
- data/lib/proscenium/middleware/base.rb +0 -27
- data/lib/proscenium/middleware/chunks.rb +27 -0
- data/lib/proscenium/middleware/esbuild.rb +1 -19
- data/lib/proscenium/middleware/vendor.rb +28 -0
- data/lib/proscenium/middleware.rb +5 -15
- data/lib/proscenium/railtie.rb +26 -5
- data/lib/proscenium/railties/assets.rake +18 -0
- data/lib/proscenium/side_load.rb +2 -8
- data/lib/proscenium/version.rb +1 -1
- data/lib/proscenium.rb +17 -2
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 223869aa06c452bb332f16d9be4254ac0a7e542da2666879164c3f5fd93344e4
|
|
4
|
+
data.tar.gz: bd6e10dc0a32400009a74628a6ec2a5bee1cdbed99839e9ca1a1978a7e60243a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 541fe7b47d257d2ba454c50a2ae587d971c610b583a8e7468862a25279029ea71cf1d722918b75131b640859a534a4dfc5af83db71c90da75380e97b7839d7f8
|
|
7
|
+
data.tar.gz: 9cae265e01be8700e82a5ff56b175b1a123d7f7304014211e423d31936bcad196a76947ae4497c0d49db5f494b356cbdc8b9efe39a9ce35ca92dfbb58046c3d6
|
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
|
|
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
|
-
```
|
|
264
|
-
{
|
|
265
|
-
"
|
|
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
|
-
##
|
|
643
|
+
## rjs is back
|
|
696
644
|
|
|
697
|
-
|
|
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
|
-
|
|
647
|
+
## Resolution
|
|
700
648
|
|
|
701
|
-
|
|
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
|
-
|
|
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
|
-
|
|
706
|
-
|
|
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
|
-
|
|
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
|
-
|
|
662
|
+
config.proscenium.aliases = {
|
|
663
|
+
"utils": "/lib/utils.js",
|
|
664
|
+
"components": "/app/components"
|
|
665
|
+
}
|
|
713
666
|
```
|
|
714
667
|
|
|
715
|
-
|
|
668
|
+
You can then import using the alias:
|
|
716
669
|
|
|
717
|
-
|
|
670
|
+
```js
|
|
671
|
+
import utils from "utils";
|
|
672
|
+
import Header from "components/header";
|
|
673
|
+
```
|
|
718
674
|
|
|
719
|
-
##
|
|
675
|
+
## Pre-compilation
|
|
720
676
|
|
|
721
|
-
Proscenium
|
|
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
|
-
|
|
679
|
+
```bash
|
|
680
|
+
rails assets:precompile
|
|
681
|
+
```
|
|
724
682
|
|
|
725
|
-
|
|
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
|
-
|
|
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
|
|
data/lib/proscenium/builder.rb
CHANGED
|
@@ -4,8 +4,6 @@ require 'ffi'
|
|
|
4
4
|
|
|
5
5
|
module Proscenium
|
|
6
6
|
class Builder
|
|
7
|
-
class CompileError < StandardError; end
|
|
8
|
-
|
|
9
7
|
ENVIRONMENTS = { development: 1, test: 2, production: 3 }.freeze
|
|
10
8
|
|
|
11
9
|
class Result < FFI::Struct
|
|
@@ -14,6 +12,11 @@ module Proscenium
|
|
|
14
12
|
:content_hash, :string
|
|
15
13
|
end
|
|
16
14
|
|
|
15
|
+
class CompileResult < FFI::Struct
|
|
16
|
+
layout :success, :bool,
|
|
17
|
+
:messages, :string
|
|
18
|
+
end
|
|
19
|
+
|
|
17
20
|
module Request
|
|
18
21
|
extend FFI::Library
|
|
19
22
|
|
|
@@ -23,7 +26,6 @@ module Proscenium
|
|
|
23
26
|
|
|
24
27
|
attach_function :build_to_string, [
|
|
25
28
|
:string, # Path or entry point.
|
|
26
|
-
:string, # cache_query_string.
|
|
27
29
|
:pointer # Config as JSON.
|
|
28
30
|
], Result.by_value
|
|
29
31
|
|
|
@@ -32,41 +34,51 @@ module Proscenium
|
|
|
32
34
|
:pointer # Config as JSON.
|
|
33
35
|
], Result.by_value
|
|
34
36
|
|
|
37
|
+
attach_function :compile, [
|
|
38
|
+
:pointer # Config as JSON.
|
|
39
|
+
], CompileResult.by_value
|
|
40
|
+
|
|
35
41
|
attach_function :reset_config, [], :void
|
|
36
42
|
end
|
|
37
43
|
|
|
38
|
-
class BuildError <
|
|
39
|
-
attr_reader :error
|
|
44
|
+
class BuildError < Error
|
|
45
|
+
attr_reader :error, :path
|
|
40
46
|
|
|
41
|
-
def initialize(error)
|
|
42
|
-
@
|
|
47
|
+
def initialize(path, error)
|
|
48
|
+
@path = path
|
|
49
|
+
@error = JSON.parse(error, strict: true)
|
|
43
50
|
|
|
44
|
-
msg = @error['
|
|
45
|
-
msg << ' - ' << @error['
|
|
46
|
-
if (location = @error['
|
|
47
|
-
msg << " at #{location['
|
|
51
|
+
msg = @error['Text']
|
|
52
|
+
msg << ' - ' << @error['Detail'] if @error['Detail'].is_a?(String)
|
|
53
|
+
if (location = @error['Location'])
|
|
54
|
+
msg << " at #{location['File']}:#{location['Line']}:#{location['Column']}"
|
|
48
55
|
end
|
|
49
56
|
|
|
50
|
-
super(msg)
|
|
57
|
+
super("Failed to build #{path} - #{msg}")
|
|
51
58
|
end
|
|
52
59
|
end
|
|
53
60
|
|
|
54
|
-
class ResolveError <
|
|
55
|
-
attr_reader :
|
|
61
|
+
class ResolveError < Error
|
|
62
|
+
attr_reader :path
|
|
56
63
|
|
|
57
|
-
def initialize(path,
|
|
58
|
-
|
|
64
|
+
def initialize(path, msg)
|
|
65
|
+
@path = path
|
|
66
|
+
super("Failed to resolve #{path} - #{msg}")
|
|
59
67
|
end
|
|
60
68
|
end
|
|
61
69
|
|
|
62
|
-
def self.build_to_string(path,
|
|
63
|
-
new(root:).build_to_string(path
|
|
70
|
+
def self.build_to_string(path, root: nil)
|
|
71
|
+
new(root:).build_to_string(path)
|
|
64
72
|
end
|
|
65
73
|
|
|
66
74
|
def self.resolve(path, root: nil)
|
|
67
75
|
new(root:).resolve(path)
|
|
68
76
|
end
|
|
69
77
|
|
|
78
|
+
def self.compile(root: nil)
|
|
79
|
+
new(root:).compile
|
|
80
|
+
end
|
|
81
|
+
|
|
70
82
|
# Intended for tests only.
|
|
71
83
|
def self.reset_config!
|
|
72
84
|
Request.reset_config
|
|
@@ -75,6 +87,7 @@ module Proscenium
|
|
|
75
87
|
def initialize(root: nil)
|
|
76
88
|
@request_config = FFI::MemoryPointer.from_string({
|
|
77
89
|
RootPath: (root || Rails.root).to_s,
|
|
90
|
+
OutputDir: "public#{Proscenium.config.output_dir}",
|
|
78
91
|
GemPath: gem_root,
|
|
79
92
|
Environment: ENVIRONMENTS.fetch(Rails.env.to_sym, 2),
|
|
80
93
|
EnvVars: env_vars,
|
|
@@ -82,16 +95,17 @@ module Proscenium
|
|
|
82
95
|
RubyGems: Proscenium::BundledGems.paths,
|
|
83
96
|
Bundle: Proscenium.config.bundle,
|
|
84
97
|
Aliases: Proscenium.config.aliases,
|
|
85
|
-
|
|
98
|
+
External: Proscenium.config.external,
|
|
99
|
+
Precompile: Proscenium.config.precompile,
|
|
86
100
|
Debug: Proscenium.config.debug
|
|
87
101
|
}.to_json)
|
|
88
102
|
end
|
|
89
103
|
|
|
90
|
-
def build_to_string(path
|
|
104
|
+
def build_to_string(path)
|
|
91
105
|
ActiveSupport::Notifications.instrument('build.proscenium', identifier: path) do
|
|
92
|
-
result = Request.build_to_string(path,
|
|
106
|
+
result = Request.build_to_string(path, @request_config)
|
|
93
107
|
|
|
94
|
-
raise BuildError, result[:response] unless result[:success]
|
|
108
|
+
raise BuildError.new(path, result[:response]) unless result[:success]
|
|
95
109
|
|
|
96
110
|
result
|
|
97
111
|
end
|
|
@@ -107,6 +121,11 @@ module Proscenium
|
|
|
107
121
|
end
|
|
108
122
|
end
|
|
109
123
|
|
|
124
|
+
def compile
|
|
125
|
+
result = Request.compile(@request_config)
|
|
126
|
+
result[:success]
|
|
127
|
+
end
|
|
128
|
+
|
|
110
129
|
private
|
|
111
130
|
|
|
112
131
|
# Build the ENV variables as determined by `Proscenium.config.env_vars` and
|
|
@@ -7,7 +7,7 @@ module Proscenium::CssModule
|
|
|
7
7
|
autoload :Transformer
|
|
8
8
|
autoload :Rewriter
|
|
9
9
|
|
|
10
|
-
class TransformError <
|
|
10
|
+
class TransformError < Proscenium::Error
|
|
11
11
|
def initialize(name, additional_msg = nil)
|
|
12
12
|
msg = "Failed to transform CSS module `#{name}`"
|
|
13
13
|
msg << ' - ' << additional_msg if additional_msg
|
|
Binary file
|
|
@@ -25,6 +25,10 @@ struct Result {
|
|
|
25
25
|
int success;
|
|
26
26
|
char* response;
|
|
27
27
|
char* contentHash;
|
|
28
|
+
};
|
|
29
|
+
struct CompileResult {
|
|
30
|
+
int success;
|
|
31
|
+
char* messages;
|
|
28
32
|
};
|
|
29
33
|
|
|
30
34
|
#line 1 "cgo-generated-wrapper"
|
|
@@ -90,7 +94,7 @@ extern void reset_config();
|
|
|
90
94
|
// - path - The path to build relative to `root`.
|
|
91
95
|
// - config
|
|
92
96
|
//
|
|
93
|
-
extern struct Result build_to_string(char* filePath, char*
|
|
97
|
+
extern struct Result build_to_string(char* filePath, char* configJson);
|
|
94
98
|
|
|
95
99
|
// Resolve the given `path` relative to the `root`.
|
|
96
100
|
//
|
|
@@ -99,6 +103,12 @@ extern struct Result build_to_string(char* filePath, char* cacheQueryString, cha
|
|
|
99
103
|
//
|
|
100
104
|
extern struct Result resolve(char* filePath, char* configJson);
|
|
101
105
|
|
|
106
|
+
// Compile assets using the given `config`.
|
|
107
|
+
//
|
|
108
|
+
// - config
|
|
109
|
+
//
|
|
110
|
+
extern struct CompileResult compile(char* configJson);
|
|
111
|
+
|
|
102
112
|
#ifdef __cplusplus
|
|
103
113
|
}
|
|
104
114
|
#endif
|
data/lib/proscenium/helper.rb
CHANGED
|
@@ -11,18 +11,9 @@ module Proscenium
|
|
|
11
11
|
end
|
|
12
12
|
end
|
|
13
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.
|
|
18
|
-
#
|
|
19
|
-
# If the given `path` is a bare path (does not start with `./` or `../`), then we use
|
|
20
|
-
# Rails default conventions, and serve CSS from /app/assets/stylesheets and JS from
|
|
21
|
-
# /app/javascript.
|
|
22
14
|
def compute_asset_path(path, options = {})
|
|
23
15
|
if %i[javascript stylesheet].include?(options[:type])
|
|
24
|
-
|
|
25
|
-
return path
|
|
16
|
+
return Proscenium::Manifest[path] || "/#{path}"
|
|
26
17
|
end
|
|
27
18
|
|
|
28
19
|
super
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Proscenium
|
|
4
|
+
module Manifest
|
|
5
|
+
mattr_accessor :manifest, default: {}
|
|
6
|
+
mattr_accessor :loaded, default: false
|
|
7
|
+
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def loaded?
|
|
11
|
+
loaded
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def load!
|
|
15
|
+
self.manifest = {}
|
|
16
|
+
self.loaded = false
|
|
17
|
+
|
|
18
|
+
if Proscenium.config.manifest_path.exist?
|
|
19
|
+
self.loaded = true
|
|
20
|
+
|
|
21
|
+
JSON.parse(Proscenium.config.manifest_path.read)['outputs'].each do |output_path, details|
|
|
22
|
+
next if !details.key?('entryPoint')
|
|
23
|
+
|
|
24
|
+
manifest[details['entryPoint']] = "/#{output_path.delete_prefix('public/')}"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
manifest
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def reset!
|
|
32
|
+
self.manifest = {}
|
|
33
|
+
self.loaded = false
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def [](key)
|
|
37
|
+
loaded? ? manifest[key] : "/#{key}"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -5,18 +5,6 @@ module Proscenium
|
|
|
5
5
|
class Base
|
|
6
6
|
include ActiveSupport::Benchmarkable
|
|
7
7
|
|
|
8
|
-
# Error when the result of the build returns an error. For example, when esbuild returns
|
|
9
|
-
# errors.
|
|
10
|
-
class CompileError < StandardError
|
|
11
|
-
attr_reader :detail, :file
|
|
12
|
-
|
|
13
|
-
def initialize(args)
|
|
14
|
-
@detail = args[:detail]
|
|
15
|
-
@file = args[:file]
|
|
16
|
-
super("Failed to build '#{args[:file]}' -- #{detail}")
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
|
|
20
8
|
def self.attempt(request)
|
|
21
9
|
new(request).renderable!&.attempt
|
|
22
10
|
end
|
|
@@ -40,17 +28,6 @@ module Proscenium
|
|
|
40
28
|
@path_to_build ||= @request.path[1..]
|
|
41
29
|
end
|
|
42
30
|
|
|
43
|
-
def cache_query_string
|
|
44
|
-
@cache_query_string ||= begin
|
|
45
|
-
params = @request.query_parameters
|
|
46
|
-
if params.one? && params.first[0] != '' && params.first[1].nil?
|
|
47
|
-
params.keys.first
|
|
48
|
-
else
|
|
49
|
-
Proscenium.config.cache_query_string
|
|
50
|
-
end
|
|
51
|
-
end.presence || ''
|
|
52
|
-
end
|
|
53
|
-
|
|
54
31
|
def sourcemap?
|
|
55
32
|
@request.path.ends_with?('.map')
|
|
56
33
|
end
|
|
@@ -97,10 +74,6 @@ module Proscenium
|
|
|
97
74
|
response.content_type = content_type
|
|
98
75
|
response.etag = result[:content_hash]
|
|
99
76
|
|
|
100
|
-
if !cache_query_string.blank? && Proscenium.config.cache_max_age
|
|
101
|
-
response.cache! Proscenium.config.cache_max_age
|
|
102
|
-
end
|
|
103
|
-
|
|
104
77
|
if @request.fresh?(response)
|
|
105
78
|
response.status = 304
|
|
106
79
|
response.body = []
|
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
'SourceMap' => "#{request.path}.map",
|
|
20
|
+
'Cache-Control' => "public, max-age=#{100.years}, immutable",
|
|
21
|
+
'ETag' => request.path.match(/-\$([a-z0-9]+)\$/i)[1]
|
|
22
|
+
}
|
|
23
|
+
).attempt(request.env) || @app.call(env)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -3,26 +3,8 @@
|
|
|
3
3
|
module Proscenium
|
|
4
4
|
class Middleware
|
|
5
5
|
class Esbuild < Base
|
|
6
|
-
class CompileError < Base::CompileError
|
|
7
|
-
def initialize(args)
|
|
8
|
-
detail = args[:detail]
|
|
9
|
-
detail = JSON.parse(detail, mode: :strict)
|
|
10
|
-
|
|
11
|
-
args['detail'] = if detail['location']
|
|
12
|
-
"#{detail['text']} in #{detail['location']['file']}:" +
|
|
13
|
-
detail['location']['line'].to_s
|
|
14
|
-
else
|
|
15
|
-
detail['text']
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
super
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
|
|
22
6
|
def attempt
|
|
23
|
-
render_response Builder.build_to_string(path_to_build
|
|
24
|
-
rescue Builder::CompileError => e
|
|
25
|
-
raise self.class::CompileError, { file: @request.fullpath, detail: e.message }, caller
|
|
7
|
+
render_response Builder.build_to_string(path_to_build)
|
|
26
8
|
end
|
|
27
9
|
end
|
|
28
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
|
|
@@ -4,11 +4,13 @@ module Proscenium
|
|
|
4
4
|
class Middleware
|
|
5
5
|
extend ActiveSupport::Autoload
|
|
6
6
|
|
|
7
|
-
class BuildError <
|
|
7
|
+
class BuildError < Error; end
|
|
8
8
|
|
|
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
|
-
|
|
24
|
-
# cache lifetime, since these are content-hashed and will never change.
|
|
25
|
-
if request.path.match?(CHUNKS_PATH)
|
|
26
|
-
::ActionDispatch::FileHandler.new(
|
|
27
|
-
Rails.public_path.join('assets').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
|
|
33
|
+
type.attempt(request)
|
|
44
34
|
end
|
|
45
35
|
|
|
46
36
|
def find_type(request)
|
data/lib/proscenium/railtie.rb
CHANGED
|
@@ -15,10 +15,10 @@ 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
|
-
|
|
21
18
|
config.proscenium.aliases = {}
|
|
19
|
+
config.proscenium.external = Set['*.rjs', '*.gif', '*.jpg', '*.png', '*.woff2', '*.woff']
|
|
20
|
+
config.proscenium.precompile = Set.new
|
|
21
|
+
config.proscenium.output_dir = '/assets'
|
|
22
22
|
|
|
23
23
|
# List of environment variable names that should be passed to the builder, which will then be
|
|
24
24
|
# passed to esbuild's `Define` option. Being explicit about which environment variables are
|
|
@@ -29,7 +29,13 @@ module Proscenium
|
|
|
29
29
|
'Proscenium::Builder::BuildError' => 'build_error'
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
config.after_initialize do |
|
|
32
|
+
config.after_initialize do |app|
|
|
33
|
+
config.proscenium.output_path ||=
|
|
34
|
+
Pathname.new(File.join(app.config.paths['public'].first, app.config.proscenium.output_dir))
|
|
35
|
+
config.proscenium.manifest_path = config.proscenium.output_path.join('.manifest.json')
|
|
36
|
+
|
|
37
|
+
Proscenium::Manifest.load!
|
|
38
|
+
|
|
33
39
|
if config.proscenium.logging
|
|
34
40
|
require 'proscenium/log_subscriber'
|
|
35
41
|
Proscenium::LogSubscriber.attach_to :proscenium
|
|
@@ -49,7 +55,18 @@ module Proscenium
|
|
|
49
55
|
unless config.proscenium.logging
|
|
50
56
|
app.middleware.insert_before Rails::Rack::Logger, Proscenium::Middleware::SilenceRequest
|
|
51
57
|
end
|
|
52
|
-
|
|
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
|
|
53
70
|
end
|
|
54
71
|
|
|
55
72
|
initializer 'proscenium.sideloading' do
|
|
@@ -65,5 +82,9 @@ module Proscenium
|
|
|
65
82
|
ActionView::PartialRenderer.prepend Monkey::PartialRenderer
|
|
66
83
|
end
|
|
67
84
|
end
|
|
85
|
+
|
|
86
|
+
rake_tasks do
|
|
87
|
+
load 'proscenium/railties/assets.rake'
|
|
88
|
+
end
|
|
68
89
|
end
|
|
69
90
|
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
namespace :assets do
|
|
4
|
+
desc 'Compile Proscenium assets'
|
|
5
|
+
task precompile: :environment do
|
|
6
|
+
puts "\nPre-compiling assets..."
|
|
7
|
+
|
|
8
|
+
raise 'Assets pre-compilation failed!' unless Proscenium::Builder.compile
|
|
9
|
+
|
|
10
|
+
puts "\nAssets pre-compiled successfully."
|
|
11
|
+
|
|
12
|
+
if Rails.env.development?
|
|
13
|
+
puts "\nWarning: You are precompiling assets in development. Rails will not " \
|
|
14
|
+
'serve any changed assets until you delete ' \
|
|
15
|
+
"public#{Rails.application.config.proscenium.output_dir}/.manifest.json"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
data/lib/proscenium/side_load.rb
CHANGED
|
@@ -40,10 +40,7 @@ module Proscenium
|
|
|
40
40
|
opts[:preload_links_header] = false if fragments
|
|
41
41
|
opts[:data] ||= {}
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
path += "?#{Proscenium.config.cache_query_string}"
|
|
45
|
-
end
|
|
46
|
-
out << helpers.stylesheet_link_tag(path, extname: false, **opts)
|
|
43
|
+
out << helpers.stylesheet_link_tag(path.delete_prefix('/'), extname: false, **opts)
|
|
47
44
|
end
|
|
48
45
|
|
|
49
46
|
if fragments
|
|
@@ -71,10 +68,7 @@ module Proscenium
|
|
|
71
68
|
opts = opts[:js].is_a?(Hash) ? opts[:js] : {}
|
|
72
69
|
opts[:preload_links_header] = false if fragments
|
|
73
70
|
|
|
74
|
-
|
|
75
|
-
path += "?#{Proscenium.config.cache_query_string}"
|
|
76
|
-
end
|
|
77
|
-
out << helpers.javascript_include_tag(path, extname: false, **opts)
|
|
71
|
+
out << helpers.javascript_include_tag(path.delete_prefix('/'), extname: false, **opts)
|
|
78
72
|
end
|
|
79
73
|
|
|
80
74
|
if fragments
|
data/lib/proscenium/version.rb
CHANGED
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,
|
|
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/}
|
|
@@ -26,6 +27,7 @@ module Proscenium
|
|
|
26
27
|
autoload :Utils
|
|
27
28
|
autoload :Monkey
|
|
28
29
|
autoload :Middleware
|
|
30
|
+
autoload :Manifest
|
|
29
31
|
autoload :EnsureLoaded
|
|
30
32
|
autoload :SideLoad
|
|
31
33
|
autoload :CssModule
|
|
@@ -44,7 +46,20 @@ module Proscenium
|
|
|
44
46
|
end
|
|
45
47
|
end
|
|
46
48
|
|
|
47
|
-
class
|
|
49
|
+
class Error < StandardError; end
|
|
50
|
+
|
|
51
|
+
class MissingAssetError < Error
|
|
52
|
+
def initialize(path)
|
|
53
|
+
super
|
|
54
|
+
@path = path
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def message
|
|
58
|
+
"The asset '#{@path}' was not found."
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
class PathResolutionFailed < Error
|
|
48
63
|
def initialize(path)
|
|
49
64
|
@path = path
|
|
50
65
|
super
|
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.
|
|
4
|
+
version: 0.22.0
|
|
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: 2025-
|
|
11
|
+
date: 2025-11-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ffi
|
|
@@ -66,13 +66,17 @@ files:
|
|
|
66
66
|
- lib/proscenium/helper.rb
|
|
67
67
|
- lib/proscenium/importer.rb
|
|
68
68
|
- lib/proscenium/log_subscriber.rb
|
|
69
|
+
- lib/proscenium/manifest.rb
|
|
69
70
|
- lib/proscenium/middleware.rb
|
|
70
71
|
- lib/proscenium/middleware/base.rb
|
|
72
|
+
- lib/proscenium/middleware/chunks.rb
|
|
71
73
|
- lib/proscenium/middleware/esbuild.rb
|
|
72
74
|
- lib/proscenium/middleware/ruby_gems.rb
|
|
73
75
|
- lib/proscenium/middleware/silence_request.rb
|
|
76
|
+
- lib/proscenium/middleware/vendor.rb
|
|
74
77
|
- lib/proscenium/monkey.rb
|
|
75
78
|
- lib/proscenium/railtie.rb
|
|
79
|
+
- lib/proscenium/railties/assets.rake
|
|
76
80
|
- lib/proscenium/react-manager/index.jsx
|
|
77
81
|
- lib/proscenium/react-manager/react.js
|
|
78
82
|
- lib/proscenium/react_componentable.rb
|