proscenium 0.16.0 → 0.19.0.beta2

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: ea20e1bed70777b024924449c5ea927bf535035c261f9d3381120c3a4346550d
4
- data.tar.gz: fcc83bddd40e876b0da2ebd13892216dbf4a430cfcf037ae016530b9d8b7cf20
3
+ metadata.gz: c0ef99a0b3eea897d899b62e5cd298770db886a8271376eb04c8686a6078fcc9
4
+ data.tar.gz: 9787122129710ba85c76f6c762804d2e50ff6b7667a7949bc016f044f10cb983
5
5
  SHA512:
6
- metadata.gz: b654f721f1fcfae87e3c0b6533dfd05a436343b81f7d67d32b12b0f29696d2d78f3da1817516d4fcd1c0263cf344504704ecbccbe1dad01a3dc3f99dcc23682a
7
- data.tar.gz: 53b9089d2ee0e2d79867cbe4c09316ac6a724355a0cf87aed4fcd1483273e19b99b0f2a256e2672e2cd1a492a9f310e86f2a779a5048ab81b7b88d43bbc82890
6
+ metadata.gz: e5a4d81385d795d350681e8150bccccd84fea0aaae377cf6ae060754fd3d1fb05eae207be86fb2390d8d75bac7e5d1351e4c5cbc0f38a4320e7b2616930fbae2
7
+ data.tar.gz: 58099ab856fa75ddacaee87c185ba228fa49e681525cc0711f0dded22b34535caf49508db53bbe2726b8c147d7e941398dd55079f13a106fd8c24d69ee053e4a
data/README.md CHANGED
@@ -1,13 +1,20 @@
1
- # Proscenium - Modern client-side development for Rails
1
+ # Proscenium - Integrated Frontend Development for Rails
2
2
 
3
- Proscenium treats your client-side code as first class citizens of your Rails app, and assumes a "fast by default" internet. It bundles your JavaScript and CSS in real time, on demand, and with zero configuration.
3
+ > 🗣️ prow · see · nee · uhm
4
+ >
5
+ > _noun_: **proscenium**
6
+ >
7
+ > - _the part of a theatre stage in front of the curtain._
8
+
9
+ **_Proscenium_** treats your frontend and client-side code as first class citizens of your Rails app, and assumes a "fast by default" internet. It bundles and minifies JavaScript (+ JSX), TypeScript (+TSX) and CSS in real time, on demand, and with zero configuration.
4
10
 
5
11
  **The highlights:**
6
12
 
7
- - Fast real-time bundling, tree-shaking, code-splitting and minification of Javascript (.js,.jsx), Typescript (.ts,.tsx) and CSS (.css).
8
- - NO JavaScript runtime needed - just the browser!
13
+ - Fast, real-time bundling, tree-shaking, code-splitting and minification of Javascript (.js,.jsx), Typescript (.ts,.tsx) and CSS (.css).
14
+ - NO JavaScript runtime needed (eg. Node) - just the browser!
9
15
  - NO build step or pre-compilation.
10
- - NO additional process or server - Just run Rails!
16
+ - NO additional process or server - Just run `rails s`!
17
+ - Transforms newer JavaScript and CSS syntax to older syntax for older browsers.
11
18
  - Deep integration with Rails.
12
19
  - Automatically side-load your layouts, views, and partials.
13
20
  - Import from NPM, URL's, and locally.
@@ -22,7 +29,6 @@ Proscenium treats your client-side code as first class citizens of your Rails ap
22
29
  - [Client-Side Code Anywhere](#client-side-code-anywhere)
23
30
  - [Side Loading](#side-loading)
24
31
  - [Importing](#importing-assets)
25
- - [URL Imports](#url-imports)
26
32
  - [Local Imports](#local-imports)
27
33
  - [Import Maps](#import-maps)
28
34
  - [Source Maps](#source-maps)
@@ -70,7 +76,9 @@ Add this line to your Rails application's Gemfile, and you're good to go:
70
76
  gem 'proscenium'
71
77
  ```
72
78
 
73
- Please note that Proscenium is designed solely for use with Rails, so will not work - at least out of the box - anywhere else.
79
+ Please note that Proscenium is designed solely for use with Rails.
80
+
81
+ Now if you start your Rails app, you can open any front end code (JS, CSS, etc.). For example, a file at `app/assets/stylesheets/application.css` can be accessed at `https://localhost:3000/app/assets/stylesheets/application.css`, which will be bundled, transformed, and minified [in production] in real time.
74
82
 
75
83
  ## Client-Side Code Anywhere
76
84
 
@@ -82,23 +90,21 @@ Simply put your JS(X) and CSS anywhere you want, and they will be served by your
82
90
 
83
91
  Using the examples above...
84
92
 
85
- - `app/views/users/index.js` => `https://yourapp.com/app/views/users/index.js`
86
- - `app/views/layouts/application.css` => `https://yourapp.com/app/views/layouts/application.css`
87
- - `lib/utils.js` => `https://yourapp.com/lib/utils.js`
88
- - `app/components/menu_component.jsx` => `https://yourapp.com/app/components/menu_component.jsx`
89
- - `config/properties.css` => `https://yourapp.com/config/properties.css`
93
+ - `app/views/users/index.js` => `https://localhost:3000/app/views/users/index.js`
94
+ - `app/views/layouts/application.css` => `https://localhost:3000/app/views/layouts/application.css`
95
+ - `lib/utils.js` => `https://localhost:3000/lib/utils.js`
96
+ - `app/components/menu_component.jsx` => `https://localhost:3000/app/components/menu_component.jsx`
97
+ - `config/properties.css` => `https://localhost:3000/config/properties.css`
90
98
 
91
99
  ## Side Loading
92
100
 
93
- > Prior to **0.10.0**, only assets with the extension `.js`, `.ts` and `.css` were side loaded. From 0.10.0, all assets are side loaded, including `.jsx`, `.tsx`, and `.module.css`. Also partials were not side loaded prior to 0.10.0.
94
-
95
- Proscenium is best experienced when you side load your assets.
101
+ Proscenium is best experienced when your assets are automtically side loaded.
96
102
 
97
103
  ### The Problem
98
104
 
99
105
  With Rails you would typically declaratively load your JavaScript and CSS assets using the `javascript_include_tag` and `stylesheet_link_tag` helpers.
100
106
 
101
- For example, you may have top-level "application" CSS located in a file at `/app/assets/application.css`. Likewise, you may have some global JavaScript located in a file at `/app/assets/application.js`.
107
+ For example, you may have top-level "application" CSS located in a file at `/app/assets/stylesheets/application.css`. Likewise, you may have some global JavaScript located in a file at `/app/javascript/application.js`.
102
108
 
103
109
  You would manually and declaratively include those two files in your application layout, something like this:
104
110
 
@@ -177,45 +183,55 @@ Now, in your layout and view, replace the `javascript_include_tag` and `styleshe
177
183
  </html>
178
184
  ```
179
185
 
180
- On each page request, Proscenium will check if any of your views, layouts and partials have a
181
- JS/TS/CSS file of the same name, and then include them wherever your placed the `include_assets`
182
- helper.
186
+ On each page request, Proscenium will check if any of your views, layouts and partials have a JS/TS/CSS file of the same name, and then include them wherever your placed the `include_assets` helper.
187
+
188
+ Now you never have to remember to include your assets again. Just create them alongside your views, partials and layouts, and Proscenium will take care of the rest.
189
+
190
+ Side loading is enabled by default, but you can disable it by setting `config.proscenium.side_load` to `false` in your `/config/application.rb`.
191
+
192
+ There are also `include_stylesheets` and `include_javascripts` helpers to allow you to control where the CSS and JS assets are included in the HTML. These helpers should be used instead of `include_assets` if you want to control exactly where the assets are included.
183
193
 
184
- Now you never have to remember to include your assets again. Just create them alongside your views,
185
- partials and layouts, and Proscenium will take care of the rest.
194
+ ## Bundling
186
195
 
187
- Side loading is enabled by default, but you can disable it by setting `config.proscenium.side_load`
188
- to `false` in your `/config/application.rb`.
196
+ To bundle a file means to inline any imported dependencies into the file itself. This process is recursive so dependencies of dependencies (and so on) will also be inlined.
189
197
 
190
- There are also `include_stylesheets` and `include_javascripts` helpers to allow you to control where
191
- the CSS and JS assets are included in the HTML. These helpers should be used instead of
192
- `include_assets` if you want to control exactly where the assets are included.
198
+ Proscenium will bundle by default, and in real time. So there is no separate build step or pre-compilation.
193
199
 
194
- ## Importing Assets
200
+ Proscenium supports importing JS, JSX, TS, TSX, CSS and SVG from NPM, by URL, your local app, and even from other Ruby Gems.
195
201
 
196
- Proscenium supports importing JS, JSX, TS, TSX, CSS and SVG from NPM, by URL, your local app, and even from Ruby Gems.
202
+ Both static ([`import`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import)) and dynamic ([`import()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import)) imports are supported for JavaScript and TypeScript, and can be used to import JS, TS, JSX, TSX, JSON, CSS and SVG files.
197
203
 
198
- Imported files are bundled together in real time. So no build step or pre-compilation is needed.
204
+ The [`@import`](https://developer.mozilla.org/en-US/docs/Web/CSS/@import) CSS at-rule is supported for CSS.
199
205
 
200
- Imports are assumed to be JS files, so there is no need to specify the file extesnion in such cases. But you can if you like. All other file types must be specified using their full file name and extension.
206
+ ### Non-analyzable imports
201
207
 
202
- ### URL Imports
208
+ Import paths are currently only bundled if they are a string literal or a glob pattern. Other forms of import paths are not bundled, and are instead preserved verbatim in the generated output. This is because bundling is a compile-time operation and Proscenium doesn't support all forms of run-time path resolution.
203
209
 
204
- Any import beginning with `http://` or `https://` will be fetched from the URL provided. For example:
210
+ Here are some examples:
205
211
 
206
212
  ```js
207
- import React from "https://esm.sh/react";
208
- ```
213
+ // Analyzable imports (will be bundled)
214
+ import "pkg";
215
+ import("pkg");
216
+ import(`./locale-${foo}.json`);
209
217
 
210
- ```css
211
- @import "https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css";
218
+ // Non-analyzable imports (will not be bundled)
219
+ import(`pkg/${foo}`);
212
220
  ```
213
221
 
214
- URL imports are cached, so that each import is only fetched once per server restart.
222
+ The way to work around non-analyzable imports is to mark the package containing this problematic code as [unbundled](#Unbundling) so that it's not included in the bundle. You will then need to ensure that a copy of the external package is available to your bundled code at run-time.
215
223
 
216
224
  ### Import from NPM (`node_modules`)
217
225
 
218
- Bare imports (imports not beginning with `./`, `/`, `https://`, `http://`) are fully supported, and will use your package manager of choice (eg, NPM, Yarn, pnpm) via the `package.json` file:
226
+ Bare imports (imports not beginning with `./`, `/`, `https://`, `http://`) are fully supported, and will use your package manager of choice (eg, NPM, Yarn, pnpm) via the `package.json` file located at the root of your Rails app.
227
+
228
+ Install the package you want to import using your package manager of choice...
229
+
230
+ ```
231
+ npm install react
232
+ ```
233
+
234
+ ...and then import it as you would any other package.
219
235
 
220
236
  ```js
221
237
  import React from "react";
@@ -223,26 +239,18 @@ import React from "react";
223
239
 
224
240
  ### Local Imports
225
241
 
226
- And of course you can import your own code, using relative or absolute paths (file extension is optional):
242
+ And of course you can import your own code, using relative or absolute paths (file extension is optional, and absolute paths use your Rails root as the base):
227
243
 
228
- ```js /app/views/layouts/application.js
244
+ ```js
229
245
  import utils from "/lib/utils";
230
- ```
231
-
232
- ```js /lib/utils.js
233
246
  import constants from "./constants";
247
+ import Header from "/app/components/header";
234
248
  ```
235
249
 
236
- ```css /app/views/layouts/application.css
250
+ ```css
237
251
  @import "/lib/reset";
238
252
  ```
239
253
 
240
- ```css /lib/reset.css
241
- body {
242
- /* some styles... */
243
- }
244
- ```
245
-
246
254
  ### Unbundling
247
255
 
248
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 this cases, you can use the `unbundle` prefix
@@ -253,7 +261,7 @@ import React from "unbundle:react";
253
261
 
254
262
  This only works any bare and local imports.
255
263
 
256
- You can also use the `unbundle` prefix in your import map, which ensures that all imports of a particular path is always unbundled:
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:
257
265
 
258
266
  ```json
259
267
  {
@@ -411,7 +419,7 @@ import translations from "@proscenium/i18n";
411
419
 
412
420
  ## Javascript
413
421
 
414
- By default, Proscenium's output will take advantage of all modern JS features. For example, `a !== void 0 && a !== null ? a : b` will become `a ?? b` when minifying (enabled by default in production), which makes use of syntax from the ES2020 version of JavaScript.
422
+ By default, Proscenium's output will take advantage of all modern JS features from the ES2022 spec and earlier. For example, `a !== void 0 && a !== null ? a : b` will become `a ?? b` when minifying (enabled by default in production), which makes use of syntax from the ES2020 version of JavaScript. Any syntax feature that is not supported by ES2020 will be transformed into older JavaScript syntax that is more widely supported.
415
423
 
416
424
  ### Tree Shaking
417
425
 
@@ -491,6 +499,8 @@ Note that by default, Proscenium's output will take advantage of all modern CSS
491
499
 
492
500
  The new CSS nesting syntax is supported, and transformed into non-nested CSS for older browsers.
493
501
 
502
+ Proscenium will also automatically insert vendor prefixes so that your CSS will work in older browsers.
503
+
494
504
  ### Importing CSS from JavaScript
495
505
 
496
506
  You can also import CSS from JavaScript. When you do this, Proscenium will automatically append each stylesheet to the document's head as a `<link>` element.
@@ -835,7 +845,7 @@ Proscenium brings back RJS! Any path ending in .rjs will be served from your Rai
835
845
 
836
846
  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`.
837
847
 
838
- So a file at `/app/views/users/index.js` will be served from `https://yourapp.com/app/views/users/index.js`.
848
+ So a file at `/app/views/users/index.js` will be served from `https://localhost:3000/app/views/users/index.js`.
839
849
 
840
850
  You can continue to access any file in the `/public` directory as you normally would. Proscenium will not process files in the `/public` directory.
841
851
 
@@ -880,7 +890,7 @@ bundle exec rake compile:local
880
890
  We have tests for both Ruby and Go. To run the Ruby tests:
881
891
 
882
892
  ```bash
883
- bundle exec sus
893
+ bin/test
884
894
  ```
885
895
 
886
896
  To run the Go tests:
@@ -7,6 +7,8 @@ module Proscenium
7
7
  class Builder
8
8
  class CompileError < StandardError; end
9
9
 
10
+ ENVIRONMENTS = { development: 1, test: 2, production: 3 }.freeze
11
+
10
12
  class Result < FFI::Struct
11
13
  layout :success, :bool,
12
14
  :response, :string
@@ -19,45 +21,21 @@ module Proscenium
19
21
  enum :environment, [:development, 1, :test, :production]
20
22
 
21
23
  attach_function :build_to_string, [
22
- :string, # Path or entry point.
23
- :string, # Base URL of the Rails app. eg. https://example.com
24
- :string, # Path to import map, relative to root
25
- :string, # ENV variables as a JSON string
26
-
27
- # Config
28
- :string, # Rails application root
29
- :string, # Proscenium gem root
30
- :environment, # Rails environment as a Symbol
31
- :bool, # Code splitting enabled?
32
- :string, # Engine names and paths as a JSON string
33
- :bool # Debugging enabled?
24
+ :string, # Path or entry point.
25
+ :pointer # Config as JSON.
34
26
  ], Result.by_value
35
27
 
36
28
  attach_function :build_to_path, [
37
- :string, # Path or entry point. Multiple can be given by separating with a semi-colon
38
- :string, # Base URL of the Rails app. eg. https://example.com
39
- :string, # Path to import map, relative to root
40
- :string, # ENV variables as a JSON string
41
-
42
- # Config
43
- :string, # Rails application root
44
- :string, # Proscenium gem root
45
- :environment, # Rails environment as a Symbol
46
- :bool, # Code splitting enabled?
47
- :string, # Engine names and paths as a JSON string
48
- :bool # Debugging enabled?
29
+ :string, # Path or entry point. Multiple can be given by separating with a semi-colon
30
+ :pointer # Config as JSON.
49
31
  ], Result.by_value
50
32
 
51
33
  attach_function :resolve, [
52
- :string, # path or entry point
53
- :string, # path to import map, relative to root
54
-
55
- # Config
56
- :string, # Rails application root
57
- :string, # Proscenium gem root
58
- :environment, # Rails environment as a Symbol
59
- :bool # debugging enabled?
34
+ :string, # path or entry point
35
+ :pointer # Config as JSON.
60
36
  ], Result.by_value
37
+
38
+ attach_function :reset_config, [], :void
61
39
  end
62
40
 
63
41
  class BuildError < StandardError
@@ -83,21 +61,34 @@ module Proscenium
83
61
  end
84
62
  end
85
63
 
86
- def self.build_to_path(path, root: nil, base_url: nil)
87
- new(root:, base_url:).build_to_path(path)
64
+ def self.build_to_path(path, root: nil)
65
+ new(root:).build_to_path(path)
88
66
  end
89
67
 
90
- def self.build_to_string(path, root: nil, base_url: nil)
91
- new(root:, base_url:).build_to_string(path)
68
+ def self.build_to_string(path, root: nil)
69
+ new(root:).build_to_string(path)
92
70
  end
93
71
 
94
72
  def self.resolve(path, root: nil)
95
73
  new(root:).resolve(path)
96
74
  end
97
75
 
98
- def initialize(root: nil, base_url: nil)
99
- @root = root || Rails.root
100
- @base_url = base_url
76
+ # Intended for tests only.
77
+ def self.reset_config!
78
+ Request.reset_config
79
+ end
80
+
81
+ def initialize(root: nil)
82
+ @request_config = FFI::MemoryPointer.from_string({
83
+ RootPath: (root || Rails.root).to_s,
84
+ GemPath: gem_root,
85
+ Environment: ENVIRONMENTS.fetch(Rails.env.to_sym, 2),
86
+ Engines: engines,
87
+ EnvVars: env_vars,
88
+ CodeSplitting: Proscenium.config.code_splitting,
89
+ Bundle: Proscenium.config.bundle,
90
+ Debug: Proscenium.config.debug
91
+ }.to_json)
101
92
  end
102
93
 
103
94
  def build_to_path(path)
@@ -105,13 +96,7 @@ module Proscenium
105
96
  identifier: path,
106
97
  cached: Proscenium.cache.exist?(path)) do
107
98
  Proscenium.cache.fetch path do
108
- result = Request.build_to_path(path, @base_url, import_map, env_vars.to_json,
109
- @root.to_s,
110
- gem_root,
111
- Rails.env.to_sym,
112
- Proscenium.config.code_splitting,
113
- engines.to_json,
114
- Proscenium.config.debug)
99
+ result = Request.build_to_path(path, @request_config)
115
100
 
116
101
  raise BuildError, result[:response] unless result[:success]
117
102
 
@@ -122,13 +107,7 @@ module Proscenium
122
107
 
123
108
  def build_to_string(path)
124
109
  ActiveSupport::Notifications.instrument('build_to_string.proscenium', identifier: path) do
125
- result = Request.build_to_string(path, @base_url, import_map, env_vars.to_json,
126
- @root.to_s,
127
- gem_root,
128
- Rails.env.to_sym,
129
- Proscenium.config.code_splitting,
130
- engines.to_json,
131
- Proscenium.config.debug)
110
+ result = Request.build_to_string(path, @request_config)
132
111
 
133
112
  raise BuildError, result[:response] unless result[:success]
134
113
 
@@ -138,10 +117,8 @@ module Proscenium
138
117
 
139
118
  def resolve(path)
140
119
  ActiveSupport::Notifications.instrument('resolve.proscenium', identifier: path) do
141
- result = Request.resolve(path, import_map, @root.to_s,
142
- gem_root,
143
- Rails.env.to_sym,
144
- Proscenium.config.debug)
120
+ result = Request.resolve(path, @request_config)
121
+
145
122
  raise ResolveError.new(path, result[:response]) unless result[:success]
146
123
 
147
124
  result[:response]
@@ -168,20 +145,6 @@ module Proscenium
168
145
  end
169
146
  end
170
147
 
171
- def import_map
172
- return unless (path = Rails.root&.join('config'))
173
-
174
- if (json = path.join('import_map.json')).exist?
175
- return json.relative_path_from(@root).to_s
176
- end
177
-
178
- if (js = path.join('import_map.js')).exist?
179
- return js.relative_path_from(@root).to_s
180
- end
181
-
182
- nil
183
- end
184
-
185
148
  def gem_root
186
149
  Pathname.new(__dir__).join('..', '..').to_s
187
150
  end
Binary file
@@ -82,49 +82,28 @@ typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
82
82
  extern "C" {
83
83
  #endif
84
84
 
85
+ extern void reset_config();
85
86
 
86
- // Build the given `path` in the `root`.
87
+ // Build the given `path` using the `config`.
87
88
  //
88
- // BuildOptions
89
- // - path - The path to build relative to `root`.
90
- // - baseUrl - base URL of the Rails app. eg. https://example.com
91
- // - importMap - Path to the import map relative to `root`.
92
- // - envVars - JSON string of environment variables.
93
- // Config:
94
- // - root - The working directory.
95
- // - env - The environment (1 = development, 2 = test, 3 = production)
96
- // - codeSpitting?
97
- // - debug?
89
+ // - path - The path to build relative to `root`.
90
+ // - config
98
91
  //
99
- extern struct Result build_to_string(char* filepath, char* baseUrl, char* importMap, char* envVars, char* appRoot, char* gemPath, unsigned int env, GoUint8 codeSplitting, char* engines, GoUint8 debug);
92
+ extern struct Result build_to_string(char* filePath, char* configJson);
100
93
 
101
94
  // Build the given `path` in the `root`.
102
95
  //
103
- // BuildOptions
104
- // - path - The path to build relative to `root`. Multiple paths can be given by separating them
105
- // with a semi-colon.
106
- // - baseUrl - base URL of the Rails app. eg. https://example.com
107
- // - importMap - Path to the import map relative to `root`.
108
- // - envVars - JSON string of environment variables.
109
- // Config:
110
- // - root - The working directory.
111
- // - env - The environment (1 = development, 2 = test, 3 = production)
112
- // - codeSpitting?
113
- // - debug?
96
+ // - path - The path to build relative to `root`.
97
+ // - config
114
98
  //
115
- extern struct Result build_to_path(char* filepath, char* baseUrl, char* importMap, char* envVars, char* appRoot, char* gemPath, unsigned int env, GoUint8 codeSplitting, char* engines, GoUint8 debug);
99
+ extern struct Result build_to_path(char* filePath, char* configJson);
116
100
 
117
101
  // Resolve the given `path` relative to the `root`.
118
102
  //
119
- // ResolveOptions
120
- // - path - The path to build relative to `root`.
121
- // - importMap - Path to the import map relative to `root`.
122
- // Config
123
- // - root - The working directory.
124
- // - env - The environment (1 = development, 2 = test, 3 = production)
125
- // - debug?
103
+ // - path - The path to build relative to `root`.
104
+ // - config
126
105
  //
127
- extern struct Result resolve(char* path, char* importMap, char* appRoot, char* gemPath, unsigned int env, GoUint8 debug);
106
+ extern struct Result resolve(char* filePath, char* configJson);
128
107
 
129
108
  #ifdef __cplusplus
130
109
  }
@@ -15,9 +15,15 @@ module Proscenium
15
15
  # building with Proscenium. It's important to note that `include_assets` will not call this, as
16
16
  # those asset paths all begin with a slash, which the Rails asset helpers do not pass through to
17
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.
18
22
  def compute_asset_path(path, options = {})
19
23
  if %i[javascript stylesheet].include?(options[:type])
20
- result = Proscenium::Builder.build_to_path(path, base_url: request.base_url)
24
+ path.prepend DEFAULT_RAILS_ASSET_PATHS[options[:type]] unless path.start_with?('./', '../')
25
+
26
+ result = Proscenium::Builder.build_to_path(path)
21
27
  return result.split('::').last.delete_prefix 'public'
22
28
  end
23
29
 
@@ -53,7 +59,7 @@ module Proscenium
53
59
  end
54
60
 
55
61
  def include_stylesheets
56
- '<!-- [PROSCENIUM_STYLESHEETS] -->'.html_safe
62
+ SideLoad::CSS_COMMENT.html_safe
57
63
  end
58
64
  alias side_load_stylesheets include_stylesheets
59
65
  deprecate side_load_stylesheets: 'Use `include_stylesheets` instead', deprecator: Deprecator.new
@@ -62,7 +68,7 @@ module Proscenium
62
68
  #
63
69
  # @return [String] the HTML tags for the javascripts.
64
70
  def include_javascripts
65
- '<!-- [PROSCENIUM_LAZY_SCRIPTS] --><!-- [PROSCENIUM_JAVASCRIPTS] -->'.html_safe
71
+ (SideLoad::LAZY_COMMENT + SideLoad::JS_COMMENT).html_safe
66
72
  end
67
73
  alias side_load_javascripts include_javascripts
68
74
  deprecate side_load_javascripts: 'Use `include_javascripts` instead', deprecator: Deprecator.new
@@ -27,7 +27,7 @@ module Proscenium
27
27
  path = CGI.unescape(path) if path.start_with?(/https?%3A%2F%2F/)
28
28
 
29
29
  info do
30
- message = " #{color('[Proscenium]', nil, bold: true)} Building #{path}"
30
+ message = " #{color('[Proscenium]', nil, bold: true)} Building (to string) #{path}"
31
31
  message << " (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
32
32
  end
33
33
  end
@@ -20,8 +20,7 @@ module Proscenium
20
20
  end
21
21
 
22
22
  def attempt
23
- render_response Builder.build_to_string(path_to_build, root: Rails.root.to_s,
24
- base_url: @request.base_url)
23
+ render_response Builder.build_to_string(path_to_build)
25
24
  rescue Builder::CompileError => e
26
25
  raise self.class::CompileError, { file: @request.fullpath, detail: e.message }, caller
27
26
  end
@@ -11,7 +11,6 @@ module Proscenium
11
11
  autoload :Esbuild
12
12
  autoload :Engines
13
13
  autoload :Runtime
14
- autoload :Url
15
14
 
16
15
  def initialize(app)
17
16
  @app = app
@@ -41,7 +40,6 @@ module Proscenium
41
40
  end
42
41
 
43
42
  def find_type(request)
44
- return Url if request.path.match?(%r{^/https?%3A%2F%2F})
45
43
  return Runtime if request.path.match?(%r{^/@proscenium/})
46
44
  return Esbuild if Pathname.new(request.path).fnmatch?(app_path_glob, File::FNM_EXTGLOB)
47
45
 
@@ -11,6 +11,7 @@ module Proscenium
11
11
 
12
12
  config.proscenium = ActiveSupport::OrderedOptions.new
13
13
  config.proscenium.debug = false
14
+ config.proscenium.bundle = true
14
15
  config.proscenium.side_load = true
15
16
  config.proscenium.code_splitting = true
16
17
 
@@ -26,7 +26,11 @@ module Proscenium
26
26
  elsif path.start_with?(Proscenium.ui_path.to_s)
27
27
  path.delete_prefix Proscenium.root.join('lib').to_s
28
28
  elsif (engine = Proscenium.config.engines.find { |e| path.start_with? "#{e.root}/" })
29
- path.sub(/^#{engine.root}/, "/#{engine.engine_name}")
29
+ if engine.root.to_s.start_with?("#{Rails.root}/")
30
+ path.delete_prefix Rails.root.to_s
31
+ else
32
+ path.sub(/^#{engine.root}/, "/#{engine.engine_name}")
33
+ end
30
34
  elsif path.start_with?("#{Rails.root}/")
31
35
  path.delete_prefix Rails.root.to_s
32
36
  else
@@ -2,6 +2,10 @@
2
2
 
3
3
  module Proscenium
4
4
  class SideLoad
5
+ JS_COMMENT = '<!-- [PROSCENIUM_JAVASCRIPTS] -->'
6
+ CSS_COMMENT = '<!-- [PROSCENIUM_STYLESHEETS] -->'
7
+ LAZY_COMMENT = '<!-- [PROSCENIUM_LAZY_SCRIPTS] -->'
8
+
5
9
  module Controller
6
10
  def self.included(child)
7
11
  child.class_eval do
@@ -23,7 +27,13 @@ module Proscenium
23
27
  def capture_and_replace_proscenium_stylesheets
24
28
  return if response_body.nil?
25
29
  return if response_body.first.blank? || !Proscenium::Importer.css_imported?
26
- return unless response_body.first.include? '<!-- [PROSCENIUM_STYLESHEETS] -->'
30
+
31
+ included_comment = response_body.first.include?(CSS_COMMENT)
32
+ fragments = if (fragment_header = request.headers['X-Fragment'])
33
+ fragment_header.split
34
+ end
35
+
36
+ return if !fragments && !included_comment
27
37
 
28
38
  imports = Proscenium::Importer.imported.dup
29
39
  paths_to_build = []
@@ -31,8 +41,7 @@ module Proscenium
31
41
  paths_to_build << x.delete_prefix('/')
32
42
  end
33
43
 
34
- result = Proscenium::Builder.build_to_path(paths_to_build.join(';'),
35
- base_url: helpers.request.base_url)
44
+ result = Proscenium::Builder.build_to_path(paths_to_build.join(';'))
36
45
 
37
46
  out = []
38
47
  result.split(';').each do |x|
@@ -44,12 +53,17 @@ module Proscenium
44
53
 
45
54
  import = imports[inpath]
46
55
  opts = import[:css].is_a?(Hash) ? import[:css] : {}
56
+ opts[:preload_links_header] = false if fragments
47
57
  opts[:data] ||= {}
48
58
  opts[:data][:original_href] = inpath
49
59
  out << helpers.stylesheet_link_tag(outpath, extname: false, **opts)
50
60
  end
51
61
 
52
- response_body.first.gsub! '<!-- [PROSCENIUM_STYLESHEETS] -->', out.join.html_safe
62
+ if fragments
63
+ response_body.first.prepend out.join.html_safe
64
+ elsif included_comment
65
+ response_body.first.gsub! CSS_COMMENT, out.join.html_safe
66
+ end
53
67
  end
54
68
 
55
69
  def capture_and_replace_proscenium_javascripts
@@ -62,10 +76,15 @@ module Proscenium
62
76
  paths_to_build << x.delete_prefix('/')
63
77
  end
64
78
 
65
- result = Proscenium::Builder.build_to_path(paths_to_build.join(';'),
66
- base_url: helpers.request.base_url)
79
+ result = Proscenium::Builder.build_to_path(paths_to_build.join(';'))
67
80
 
68
- if response_body.first.include? '<!-- [PROSCENIUM_JAVASCRIPTS] -->'
81
+ included_js_comment = response_body.first.include?(JS_COMMENT)
82
+ included_lazy_comment = response_body.first.include?(LAZY_COMMENT)
83
+ fragments = if (fragment_header = request.headers['X-Fragment'])
84
+ fragment_header.split
85
+ end
86
+
87
+ if fragments || included_js_comment
69
88
  out = []
70
89
  scripts = {}
71
90
  result.split(';').each do |x|
@@ -79,14 +98,19 @@ module Proscenium
79
98
  scripts[inpath] = import.merge(outpath:)
80
99
  else
81
100
  opts = import[:js].is_a?(Hash) ? import[:js] : {}
101
+ opts[:preload_links_header] = false if fragments
82
102
  out << helpers.javascript_include_tag(outpath, extname: false, **opts)
83
103
  end
84
104
  end
85
105
 
86
- response_body.first.gsub! '<!-- [PROSCENIUM_JAVASCRIPTS] -->', out.join.html_safe
106
+ if fragments
107
+ response_body.first.prepend out.join.html_safe
108
+ elsif included_js_comment
109
+ response_body.first.gsub! JS_COMMENT, out.join.html_safe
110
+ end
87
111
  end
88
112
 
89
- return unless response_body.first.include? '<!-- [PROSCENIUM_LAZY_SCRIPTS] -->'
113
+ return if !fragments && !included_lazy_comment
90
114
 
91
115
  lazy_script = ''
92
116
  if scripts.present?
@@ -96,7 +120,11 @@ module Proscenium
96
120
  end
97
121
  end
98
122
 
99
- response_body.first.gsub! '<!-- [PROSCENIUM_LAZY_SCRIPTS] -->', lazy_script
123
+ if fragments
124
+ response_body.first.prepend lazy_script
125
+ elsif included_lazy_comment
126
+ response_body.first.gsub! LAZY_COMMENT, lazy_script
127
+ end
100
128
  end
101
129
  end
102
130
 
@@ -1,6 +1,6 @@
1
1
  @layer proscenium-ui-component {
2
2
  /*
3
- * Custom properties:
3
+ * Properties:
4
4
  *
5
5
  * --puiBreadcrumbs--link-color: LinkText;
6
6
  * --puiBreadcrumbs--link-hover-color: HighlightText;
@@ -1,28 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'literal'
4
+
3
5
  module Proscenium::UI
4
6
  class Breadcrumbs::Component < Component
7
+ extend Literal::Properties
5
8
  include Phlex::Rails::Helpers::URLFor
6
9
 
7
10
  # The path (route) to use as the HREF for the home segment. Defaults to `:root`.
8
- option :home_path, Types::String | Types::Symbol, default: -> { :root }
11
+ prop :home_path, _Union(String, Symbol), default: -> { :root }
9
12
 
10
13
  # Assign false to hide the home segment.
11
- option :with_home, Types::Bool, default: -> { true }
12
-
13
- # One or more class name(s) for the base div element which will be appended to the default.
14
- option :class, Types::Coercible::String | Types::Array.of(Types::Coercible::String),
15
- as: :class_name, default: -> { [] }
16
-
17
- # One or more class name(s) for the base div element which will replace the default. If both
18
- # `class` and `class!` are provided, all values will be merged. Defaults to `:@base`.
19
- option :class!, Types::Coercible::String | Types::Array.of(Types::Coercible::String),
20
- as: :class_name_override, default: -> { :@base }
14
+ prop :with_home, _Boolean, default: -> { true }
21
15
 
22
16
  def view_template
23
- div class: [*class_name_override, *class_name] do
17
+ div class: :@base do
24
18
  ol do
25
- if with_home
19
+ if @with_home
26
20
  li do
27
21
  home_template
28
22
  end
@@ -48,7 +42,7 @@ module Proscenium::UI
48
42
  # super { 'hello' }
49
43
  # end
50
44
  def home_template(&block)
51
- a(href: url_for(home_path)) do
45
+ a(href: url_for(@home_path)) do
52
46
  if block
53
47
  yield
54
48
  else
@@ -1,11 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry-initializer'
4
-
5
3
  module Proscenium::UI
6
4
  class Component < Proscenium::Phlex
7
5
  self.abstract_class = true
8
-
9
- extend Dry::Initializer
10
6
  end
11
7
  end
@@ -1 +1 @@
1
- console.log("/proscenium/ui/test.js");
1
+ console.log("@proscenium/ui/test.js");
@@ -2,7 +2,7 @@ export default async () => {
2
2
  window.Proscenium = window.Proscenium || {};
3
3
 
4
4
  if (!window.Proscenium.UJS) {
5
- const classPath = "/@proscenium/ujs/class.js";
5
+ const classPath = "/proscenium/ui/ujs/class.js";
6
6
  const module = await import(classPath);
7
7
  window.Proscenium.UJS = new module.default();
8
8
  }
data/lib/proscenium/ui.rb CHANGED
@@ -1,14 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry-types'
4
-
5
3
  module Proscenium::UI
6
4
  extend ActiveSupport::Autoload
7
5
 
8
6
  autoload :Component
9
7
  autoload :Breadcrumbs
10
-
11
- module Types
12
- include Dry.Types()
13
- end
14
8
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Proscenium
4
- VERSION = '0.16.0'
4
+ VERSION = '0.19.0.beta2'
5
5
  end
data/lib/proscenium.rb CHANGED
@@ -8,6 +8,13 @@ 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
+ # Default paths for Rails assets. Used by the `compute_asset_path` helper to maintain Rails
12
+ # default conventions of where JS and CSS files are located.
13
+ DEFAULT_RAILS_ASSET_PATHS = {
14
+ stylesheet: 'app/assets/stylesheets/',
15
+ javascript: 'app/javascript/'
16
+ }.freeze
17
+
11
18
  ALLOWED_DIRECTORIES = 'app,lib,config,vendor,node_modules'
12
19
 
13
20
  # Environment variables that should always be passed to the builder.
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.16.0
4
+ version: 0.19.0.beta2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel Moss
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-11 00:00:00.000000000 Z
11
+ date: 2024-12-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -30,34 +30,6 @@ dependencies:
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
32
  version: '8.0'
33
- - !ruby/object:Gem::Dependency
34
- name: dry-initializer
35
- requirement: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - "~>"
38
- - !ruby/object:Gem::Version
39
- version: '3.1'
40
- type: :runtime
41
- prerelease: false
42
- version_requirements: !ruby/object:Gem::Requirement
43
- requirements:
44
- - - "~>"
45
- - !ruby/object:Gem::Version
46
- version: '3.1'
47
- - !ruby/object:Gem::Dependency
48
- name: dry-types
49
- requirement: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - "~>"
52
- - !ruby/object:Gem::Version
53
- version: '1.7'
54
- type: :runtime
55
- prerelease: false
56
- version_requirements: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - "~>"
59
- - !ruby/object:Gem::Version
60
- version: '1.7'
61
33
  - !ruby/object:Gem::Dependency
62
34
  name: ffi
63
35
  requirement: !ruby/object:Gem::Requirement
@@ -73,33 +45,33 @@ dependencies:
73
45
  - !ruby/object:Gem::Version
74
46
  version: 1.17.0
75
47
  - !ruby/object:Gem::Dependency
76
- name: oj
48
+ name: literal
77
49
  requirement: !ruby/object:Gem::Requirement
78
50
  requirements:
79
51
  - - "~>"
80
52
  - !ruby/object:Gem::Version
81
- version: '3.13'
53
+ version: '1.0'
82
54
  type: :runtime
83
55
  prerelease: false
84
56
  version_requirements: !ruby/object:Gem::Requirement
85
57
  requirements:
86
58
  - - "~>"
87
59
  - !ruby/object:Gem::Version
88
- version: '3.13'
60
+ version: '1.0'
89
61
  - !ruby/object:Gem::Dependency
90
- name: phlex-rails
62
+ name: oj
91
63
  requirement: !ruby/object:Gem::Requirement
92
64
  requirements:
93
65
  - - "~>"
94
66
  - !ruby/object:Gem::Version
95
- version: 1.2.1
67
+ version: '3.13'
96
68
  type: :runtime
97
69
  prerelease: false
98
70
  version_requirements: !ruby/object:Gem::Requirement
99
71
  requirements:
100
72
  - - "~>"
101
73
  - !ruby/object:Gem::Version
102
- version: 1.2.1
74
+ version: '3.13'
103
75
  - !ruby/object:Gem::Dependency
104
76
  name: railties
105
77
  requirement: !ruby/object:Gem::Requirement
@@ -156,22 +128,12 @@ files:
156
128
  - lib/proscenium/ext/proscenium.h
157
129
  - lib/proscenium/helper.rb
158
130
  - lib/proscenium/importer.rb
159
- - lib/proscenium/libs/custom_element.js
160
- - lib/proscenium/libs/react-manager/index.jsx
161
- - lib/proscenium/libs/react-manager/react.js
162
- - lib/proscenium/libs/stimulus-loading.js
163
- - lib/proscenium/libs/test.js
164
- - lib/proscenium/libs/ujs/class.js
165
- - lib/proscenium/libs/ujs/data_confirm.js
166
- - lib/proscenium/libs/ujs/data_disable_with.js
167
- - lib/proscenium/libs/ujs/index.js
168
131
  - lib/proscenium/log_subscriber.rb
169
132
  - lib/proscenium/middleware.rb
170
133
  - lib/proscenium/middleware/base.rb
171
134
  - lib/proscenium/middleware/engines.rb
172
135
  - lib/proscenium/middleware/esbuild.rb
173
136
  - lib/proscenium/middleware/runtime.rb
174
- - lib/proscenium/middleware/url.rb
175
137
  - lib/proscenium/monkey.rb
176
138
  - lib/proscenium/phlex.rb
177
139
  - lib/proscenium/phlex/asset_inclusions.rb
@@ -191,7 +153,15 @@ files:
191
153
  - lib/proscenium/ui/breadcrumbs/control.rb
192
154
  - lib/proscenium/ui/breadcrumbs/mixins.css
193
155
  - lib/proscenium/ui/component.rb
156
+ - lib/proscenium/ui/custom_element.js
157
+ - lib/proscenium/ui/react-manager/index.jsx
158
+ - lib/proscenium/ui/react-manager/react.js
159
+ - lib/proscenium/ui/stimulus-loading.js
194
160
  - lib/proscenium/ui/test.js
161
+ - lib/proscenium/ui/ujs/class.js
162
+ - lib/proscenium/ui/ujs/data_confirm.js
163
+ - lib/proscenium/ui/ujs/data_disable_with.js
164
+ - lib/proscenium/ui/ujs/index.js
195
165
  - lib/proscenium/utils.rb
196
166
  - lib/proscenium/version.rb
197
167
  - lib/proscenium/view_component.rb
@@ -1 +0,0 @@
1
- console.log("/@proscenium/test.js");
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Proscenium
4
- class Middleware
5
- # Handles requests for URL encoded URL's.
6
- class Url < Esbuild
7
- private
8
-
9
- # @override [Esbuild] It's a URL, so always assume it is renderable (we won't actually know
10
- # until it's downloaded).
11
- def renderable?
12
- true
13
- end
14
- end
15
- end
16
- end
File without changes
File without changes
File without changes
File without changes
File without changes