proscenium 0.6.0-x86_64-linux → 0.7.0-x86_64-linux

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +128 -107
  3. data/lib/proscenium/css_module/class_names_resolver.rb +66 -0
  4. data/lib/proscenium/css_module/resolver.rb +76 -0
  5. data/lib/proscenium/css_module.rb +18 -39
  6. data/lib/proscenium/esbuild/golib.rb +97 -0
  7. data/lib/proscenium/esbuild.rb +32 -0
  8. data/lib/proscenium/ext/proscenium +0 -0
  9. data/lib/proscenium/ext/proscenium.h +109 -0
  10. data/lib/proscenium/helper.rb +0 -23
  11. data/lib/proscenium/log_subscriber.rb +26 -0
  12. data/lib/proscenium/middleware/base.rb +28 -36
  13. data/lib/proscenium/middleware/esbuild.rb +18 -44
  14. data/lib/proscenium/middleware/url.rb +1 -6
  15. data/lib/proscenium/middleware.rb +12 -16
  16. data/lib/proscenium/phlex/component_concerns.rb +27 -0
  17. data/lib/proscenium/phlex/page.rb +62 -0
  18. data/lib/proscenium/phlex/react_component.rb +52 -8
  19. data/lib/proscenium/phlex/resolve_css_modules.rb +67 -0
  20. data/lib/proscenium/phlex.rb +34 -33
  21. data/lib/proscenium/railtie.rb +41 -67
  22. data/lib/proscenium/side_load/ensure_loaded.rb +25 -0
  23. data/lib/proscenium/side_load/helper.rb +25 -0
  24. data/lib/proscenium/side_load/monkey.rb +48 -0
  25. data/lib/proscenium/side_load.rb +58 -52
  26. data/lib/proscenium/version.rb +1 -1
  27. data/lib/proscenium/view_component/react_component.rb +14 -0
  28. data/lib/proscenium/view_component.rb +28 -18
  29. data/lib/proscenium.rb +79 -2
  30. metadata +35 -77
  31. data/app/channels/proscenium/connection.rb +0 -13
  32. data/app/channels/proscenium/reload_channel.rb +0 -9
  33. data/bin/esbuild +0 -0
  34. data/bin/lightningcss +0 -0
  35. data/config/routes.rb +0 -7
  36. data/lib/proscenium/compiler.js +0 -84
  37. data/lib/proscenium/compilers/esbuild/argument_error.js +0 -24
  38. data/lib/proscenium/compilers/esbuild/compile_error.js +0 -148
  39. data/lib/proscenium/compilers/esbuild/css/postcss.js +0 -67
  40. data/lib/proscenium/compilers/esbuild/css_plugin.js +0 -172
  41. data/lib/proscenium/compilers/esbuild/env_plugin.js +0 -46
  42. data/lib/proscenium/compilers/esbuild/http_bundle_plugin.js +0 -53
  43. data/lib/proscenium/compilers/esbuild/import_map/parser.js +0 -178
  44. data/lib/proscenium/compilers/esbuild/import_map/read.js +0 -64
  45. data/lib/proscenium/compilers/esbuild/import_map/resolver.js +0 -95
  46. data/lib/proscenium/compilers/esbuild/import_map/utils.js +0 -25
  47. data/lib/proscenium/compilers/esbuild/resolve_plugin.js +0 -207
  48. data/lib/proscenium/compilers/esbuild/setup_plugin.js +0 -45
  49. data/lib/proscenium/compilers/esbuild/solidjs_plugin.js +0 -24
  50. data/lib/proscenium/compilers/esbuild.bench.js +0 -14
  51. data/lib/proscenium/compilers/esbuild.js +0 -179
  52. data/lib/proscenium/link_to_helper.rb +0 -40
  53. data/lib/proscenium/middleware/lightningcss.rb +0 -64
  54. data/lib/proscenium/middleware/outside_root.rb +0 -26
  55. data/lib/proscenium/middleware/runtime.rb +0 -22
  56. data/lib/proscenium/middleware/static.rb +0 -14
  57. data/lib/proscenium/phlex/component.rb +0 -9
  58. data/lib/proscenium/precompile.rb +0 -31
  59. data/lib/proscenium/runtime/auto_reload.js +0 -40
  60. data/lib/proscenium/runtime/react_shim/index.js +0 -1
  61. data/lib/proscenium/runtime/react_shim/package.json +0 -5
  62. data/lib/proscenium/utils.js +0 -12
  63. data/lib/tasks/assets.rake +0 -19
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 24989d48e852d009d61ca3407eb5c0eca0f56f9cbcd8411379deb9d3388158a7
4
- data.tar.gz: 79f44652dda8cf067b5f1df01cb3a7abb408a84f340b6910b15f933109fa6658
3
+ metadata.gz: 2c170e7b81f5a5e8591e8876e9b25cc25c0815cd453b2b14cb840229c471fdc2
4
+ data.tar.gz: 75df6068a461a45bd4cdf82b382096a25c6c79b934d27f425ac2ba1e97384b8e
5
5
  SHA512:
6
- metadata.gz: 765d537f7c14394e7aed909394161669fc3a3880b6285f5a0a7b3c42c5554b2835522a159cea97d9d241eea509035e334345547ae0c942df92cd9f15d56659d0
7
- data.tar.gz: 3d12dd75c35affe07c46ad369360fd66104799dce799ee4075931179347f7073d29c34ef3adaa3e616f75c3b14b6df5d756ef9a796fa1159936cd2cf7ec15e99
6
+ metadata.gz: e7df563fe5214e951e1d92e024d1f7de61e9a547c13a0442ea9531428b005777007b5e09605a9e88ed6f7f191e6849e2a97fbc7e429a1dfde9bb50c7a2496b59
7
+ data.tar.gz: db7286f0871be25d106442a2684ae22271f1632614a9fb0e48f19b5888311cc651ecb0ea5482734b0e7e67dba87f673d05d44d7915a3ea0408a05c24c451b2cc
data/README.md CHANGED
@@ -1,91 +1,87 @@
1
1
  # Proscenium - Modern Client-Side Tooling for Rails
2
2
 
3
3
  Proscenium treats your client-side code as first class citizens of your Rails app, and assumes a
4
- "fast by default" internet. It compiles your JS, JSX and CSS in real time, and on demand, with no
5
- configuration at all!
4
+ "fast by default" internet. It bundles your JS, JSX and CSS in real time, on demand, and with zero
5
+ configuration.
6
6
 
7
7
  - Zero configuration.
8
- - NO JavaScript rumtime needed - just the browser!
9
- - Real-time compilation.
8
+ - Fast real-time bundling, tree-shaking and minification.
9
+ - NO JavaScript runtime - just the browser!
10
+ - Deep integration with Rails.
10
11
  - No additional process or server - Just run Rails!
11
- - Serve assets from anywhere within your Rails root (/app, /config, /lib).
12
+ - Serve assets from anywhere within your Rails root (/app, /config, /lib, etc.).
12
13
  - Automatically side load JS/CSS for your layouts and views.
13
- - Import JS(X) and CSS from node_modules, URL, local (relative, absolute).
14
- - Optional bundling of JS(X) and CSS.
15
- - Import Map support for JS and CSS.
14
+ - Import JS(X), TS(X) and CSS from NPM, URL, and locally.
15
+ - Support for JSX.
16
+ - Server-side import map support.
16
17
  - CSS Modules.
17
- - CSS Custom Media Queries.
18
18
  - CSS mixins.
19
- - Minification.
20
- - Auto reload after changes (development only).
19
+ - Source maps.
20
+ - Phlex and ViewComponent integration.
21
21
 
22
- ## !! EXPERIMENTAL SOFTWARE !!
22
+ ## ⚠️ WORK IN PROGRESS ⚠️
23
23
 
24
- While my goal is to use Proscenium in production, I strongly recommended that you **DO NOT** use
25
- this in production apps! Right now, this is a play thing, and should only be used for
26
- development/testing.
24
+ While my goal is to use Proscenium in production, I recommended that you **DO NOT** use Proscenium in production just yet! It has only been run for local development, and requires several improvements for optimal production use.
27
25
 
28
26
  ## Installation
29
27
 
30
- Add this line to your application's Gemfile, and you're good to go:
28
+ Add this line to your Rails application's Gemfile, and you're good to go:
31
29
 
32
30
  ```ruby
33
31
  gem 'proscenium'
34
32
  ```
35
33
 
36
- ## Frontend Code Anywhere!
34
+ Please note that Proscenium is designed solely for use with Rails, so will not work - at least out of the box - anywhere else.
37
35
 
38
- Proscenium believes that your frontend code is just as important as your backend code, and is not an
39
- afterthought - they should be first class citizens of your Rails app. So instead of throwing all
40
- your JS and CSS into a "app/assets" directory, put them wherever you want!
36
+ ## Client-Side Code Anywhere
41
37
 
42
- For example, if you have JS that is used by your `UsersController#index`, then put it in
43
- `app/views/users/index.js`. Or if you have some CSS that is used by your entire application, put it
44
- in `app/views/layouts/application.css`. Maybe you have a few JS utility functions, so put them in
45
- `lib/utils.js`.
38
+ Proscenium believes that your frontend code is just as important as your backend code, and is not an afterthought - they should be first class citizens of your Rails app. So instead of having to throw all your JS and CSS into a "app/assets" directory, and then requiring a separate process to compile or bundle, just put them wherever you want within your app, and just run Rails!
46
39
 
47
- Simply create your JS(X) and CSS anywhere you want, and they will be served by your Rails app.
40
+ For example, if you have some JS that is required by your `app/views/users/index.html.erb` view, just create a JS file alongside it at `app/views/users/index.js`. Or if you have some CSS that is used by your entire application, put it in `app/views/layouts/application.css` and load it alongside your layout. Maybe you have a few JS utility functions, so put them in `lib/utils.js`.
41
+
42
+ Simply put your JS(X) and CSS anywhere you want, and they will be served by your Rails app from the location where you placed them.
48
43
 
49
44
  Using the examples above...
50
45
 
51
46
  - `app/views/users/index.js` => `https://yourapp.com/app/views/users/index.js`
52
47
  - `app/views/layouts/application.css` => `https://yourapp.com/app/views/layouts/application.css`
53
48
  - `lib/utils.js` => `https://yourapp.com/lib/utils.js`
49
+ - `app/components/menu_component.jsx` => `https://yourapp.com/app/components/menu_component.jsx`
54
50
  - `config/properties.css` => `https://yourapp.com/config/properties.css`
55
51
 
56
52
  ## Importing
57
53
 
58
- Proscenium supports importing JS and CSS from `node_modules`, URL, and local (relative, absolute).
54
+ Proscenium supports importing JS, JSX, TS and CSS from NPM, by URL, your local app, and even from Ruby Gems.
55
+
56
+ Imported files are bundled together in real time. So no build step or pre-compilation is needed.
59
57
 
60
- Imports are assumed to be JS files, so there is no need to specify the file extesnion in such cases.
61
- But you can if you like. All other file types must be specified using their full file name and
62
- extension.
58
+ 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.
63
59
 
64
60
  ### URL Imports
65
61
 
66
- Any import beginning with `http://` or `https://` will be fetched from the URL provided:
62
+ Any import beginning with `http://` or `https://` will be fetched from the URL provided. For example:
67
63
 
68
64
  ```js
69
- import React from 'https://esm.sh/react`
65
+ import React from 'https://esm.sh/react'
70
66
  ```
71
67
 
72
68
  ```css
73
69
  @import 'https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css';
74
70
  ```
75
71
 
72
+ URL imports are cached, so that each import is only fetched once per server restart.
73
+
76
74
  ### Import from NPM (`node_modules`)
77
75
 
78
- Bare imports (imports not beginning with `./`, `/`, `https://`, `http://`) are fully supported, and
79
- will use your package manager of choice (eg, NPM, Yarn, pnpm):
76
+ 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:
80
77
 
81
78
  ```js
82
- import React from 'react`
79
+ import React from 'react'
83
80
  ```
84
81
 
85
82
  ### Local Imports
86
83
 
87
- And of course you can import your own code, using relative or absolute paths (file extension is
88
- optional):
84
+ And of course you can import your own code, using relative or absolute paths (file extension is optional):
89
85
 
90
86
  ```js /app/views/layouts/application.js
91
87
  import utils from '/lib/utils'
@@ -95,32 +91,11 @@ import utils from '/lib/utils'
95
91
  import constants from './constants'
96
92
  ```
97
93
 
98
- ## Bundling
99
-
100
- Proscenium does not do any bundling, as we believe that **the web is now fast by default**. So we
101
- let you decide if and when to bundle your code using query parameters in your JS and CSS imports.
102
-
103
- ```js
104
- import doStuff from 'stuff?bundle'
105
- doStuff()
106
- ```
107
-
108
- Bundling a URL import is not supported, as the URL itself may also support query parameters,
109
- resulting in conflicts. For example, esm.sh also supports a `?bundle` param, bundling a module's
110
- dependencies into a single file. Instead, you should install the module locally using your favourite
111
- package manager.
112
-
113
- Note that `?bundle` will only bundle that exact path. It will not bundle any descendant imports. You
114
- can bundle all imports within a file by using the `?bundle-all` query string. Use this with caution,
115
- as you could end up swallowing everything, resulting in a very large file.
94
+ ## Import Map [WIP]
116
95
 
117
- ## Import Map
96
+ [Import map](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. If you are not familiar with import maps, think of it as a way to define aliases.
118
97
 
119
- Import map for both JS and CSS is supported out of the box, and works with no regard to the browser
120
- version being used. This is because the import map is parsed and resolved by Proscenium on the
121
- server.
122
-
123
- Just create `config/import_map.json`:
98
+ Just create `config/import_map.json` and specify the imports you want to use. For example:
124
99
 
125
100
  ```json
126
101
  {
@@ -148,9 +123,7 @@ and for CSS...
148
123
  @import '@radix-ui/colors/blue.css';
149
124
  ```
150
125
 
151
- You can instead write your import map in Javascript instead of JSON. So instead of
152
- `config/import_map.json`, create `config/import_map.js`, and define an anonymous function. This
153
- function accepts a single `environment` argument.
126
+ 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.
154
127
 
155
128
  ```js
156
129
  env => ({
@@ -160,18 +133,6 @@ env => ({
160
133
  })
161
134
  ```
162
135
 
163
- ### Aliasing
164
-
165
- You can also use the import map to define aliases:
166
-
167
- ```json
168
- {
169
- "imports": {
170
- "react": "preact/compact",
171
- }
172
- }
173
- ```
174
-
175
136
  ## Side Loading
176
137
 
177
138
  Proscenium has built in support for automatically side loading JS and CSS with your views and
@@ -203,6 +164,8 @@ to `false`.
203
164
 
204
165
  ## CSS Modules
205
166
 
167
+ Proscenium implements a subset of [CSS Modules](https://github.com/css-modules/css-modules). It supports the `:local` and `:global` keywords, but not the `composes` property. It is recommended that you use mixins instead of `composes`, as they work everywhere.
168
+
206
169
  Give any CSS file a `.module.css` extension, and Proscenium will load it as a CSS Module...
207
170
 
208
171
  ```css
@@ -211,7 +174,7 @@ Give any CSS file a `.module.css` extension, and Proscenium will load it as a CS
211
174
  }
212
175
  ```
213
176
 
214
- The above produces:
177
+ The above input produces:
215
178
 
216
179
  ```css
217
180
  .header5564cdbb {
@@ -219,63 +182,104 @@ The above produces:
219
182
  }
220
183
  ```
221
184
 
222
- Importing a CSS file from JS will automatically append the stylesheet to the document's head. The
223
- results of the import will be an object of CSS modules.
185
+ Importing a CSS file from JS will automatically append the stylesheet to the document's head. And the results of the import will be an object of CSS class to module names.
224
186
 
225
187
  ```js
226
188
  import styles from './styles.module.css'
189
+ // styles == { header: 'header5564cdbb' }
227
190
  ```
228
191
 
229
- ## Auto Reload
192
+ It is important to note that the exported object of CSS module names is actually a Proxy object. So destructuring the object will not work. Instead, you must access the properties directly.
230
193
 
231
- To aid fast development, Proscenium comes with an auto reload feature that will automatically reload
232
- the page when any files changes. It is enabled by default in development, and requires that you
233
- mount the Proscenium Railtie into your `config/routes.rb` file:
194
+ Also, importing a CSS module from another CSS module will result in the same digest string for all classes.
234
195
 
235
- ```ruby
236
- mount Proscenium::Railtie, at: '/proscenium' if Rails.env.development?
237
- ```
196
+ ## CSS Mixins
238
197
 
239
- Changes to CSS/JS(X) files in your `app` and `lib` directories will cause the page to reload.
198
+ Proscenium provides functionality for including or "mixing in" onr or more CSS classes into another. This is similar to the `composes` property of CSS Modules, but works everywhere, and is not limited to CSS Modules.
240
199
 
241
- NOTE: that this is hot module reloading (HMR) - a full page reload is triggered.
200
+ CSS mixins are supported using the `@define-mixin` and `@mixin` at-rules.
242
201
 
243
- You can disable auto reload by setting the `config.proscenium.auto_reload` config option to false.
202
+ A mixin is defined using the `@define-mixin` at-rule. Pass it a name, which should adhere to class name semantics, and declare your rules:
244
203
 
245
- ## CSS Custom Media Queries
204
+ ```css
205
+ // /lib/mixins.css
206
+ @define-mixin bigText {
207
+ font-size: 50px;
208
+ }
209
+ ```
246
210
 
247
- Proscenium supports [custom media queries](https://css-tricks.com/can-we-have-custom-media-queries-please/) as per the [spec](https://www.w3.org/TR/mediaqueries-5/#custom-mq). However, because of the way they are parsed, they cannot be imported using `@import`. So if you define your custom media queries in `/config/custom_media_queries.css`, Proscenium will automatically inject them into your CSS, so you can use them anywhere.
211
+ Use a mixin using the `@mixin` at-rule. Pass it the name of the mixin you want to use, and the url where the mixin is declared. The url is used to resolve the mixin, and can be relative, absolute, a URL, or even from an NPM packacge.
248
212
 
249
- ## CSS Mixins
213
+ ```css
214
+ // /app/views/layouts/application.css
215
+ p {
216
+ @mixin bigText from url('/lib/mixins.css');
217
+ color: red;
218
+ }
219
+ ```
250
220
 
251
- CSS mixins are supported using the `@mixin` at-rule. Simply define your mixins in any number of files ending in `.mixin.css`, and using the `@define-mixin` at-rule...
221
+ The above produce this output:
252
222
 
253
223
  ```css
254
- // /lib/text.mixin.css
255
- @define-mixin bigText {
224
+ p {
256
225
  font-size: 50px;
226
+ color: red;
257
227
  }
258
228
  ```
259
229
 
230
+ Mixins can be declared in any CSS file. They do not need to be declared in the same file as where they are used. however, if you declare and use a mixin in the same file, you don't need to specify the URL of where the mixin is declared.
231
+
260
232
  ```css
261
- // /app/views/layouts/application.css
233
+ @define-mixin bigText {
234
+ font-size: 50px;
235
+ }
236
+
262
237
  p {
263
238
  @mixin bigText;
264
239
  color: red;
265
240
  }
266
241
  ```
267
242
 
268
- ## Cache Busting
243
+ CSS modules and Mixins works perfectly together. You can include a mixin in a CSS module.
244
+
245
+ ## Importing SVG from JS(X)
246
+
247
+ Importing SVG from JS(X) will bundle the SVG source code. Additionally, if importing from JSX, the SVG source code will be rendered as a JSX component.
248
+
249
+ ## Environment Variables
250
+
251
+ Import any environment variables into your JS(X) code.
252
+
253
+ ```js
254
+ import RAILS_ENV from '@proscenium/env/RAILS_ENV'
255
+ ```
256
+
257
+ You can only access environment variables that are explicitly named. It will export `undefined` if the env variable does not exist.
258
+
259
+ ## Importing i18n
260
+
261
+ Basic support is provided for importing your Rails locale files from `config/locales/*.yml`, exporting them as JSON.
262
+
263
+ ```js
264
+ import translations from '@proscenium/i18n'
265
+ // translations.en.*
266
+ ```
267
+
268
+ ## Phlex Support
269
+
270
+ *docs needed*
269
271
 
270
- By default, all assets are not cached by the browser. But if in production, you populate the
271
- `REVISION` env variable, all CSS and JS URL's will be appended with its value as a query string, and
272
- the `Cache-Control` response header will be set to `public` and a max-age of 30 days.
272
+ ## ViewComponent Support
273
+
274
+ *docs needed*
275
+
276
+ ## Cache Busting [*COMING SOON*]
277
+
278
+ 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.
273
279
 
274
280
  For example, if you set `REVISION=v1`, URL's will be appended with `?v1`: `/my/imported/file.js?v1`.
275
281
 
276
- It is assumed that the `REVISION` env var will be unique between deploys. If it isn't, then assets
277
- will continue to be cached as the same version between deploys. I recommend you assign a version
278
- number or to use the Git commit hash of the deploy. Just make sure it is unique for each deploy.
282
+ 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.
279
283
 
280
284
  You can set the `cache_query_string` config option directly to define any query string you wish:
281
285
 
@@ -283,18 +287,29 @@ You can set the `cache_query_string` config option directly to define any query
283
287
  Rails.application.config.proscenium.cache_query_string = 'my-cache-busting-version-string'
284
288
  ```
285
289
 
286
- The cache is set with a `max-age` of 30 days. You can customise this with the `cache_max_age` config
287
- option:
290
+ The cache is set with a `max-age` of 30 days. You can customise this with the `cache_max_age` config option:
288
291
 
289
292
  ```ruby
290
293
  Rails.application.config.proscenium.cache_max_age = 12.months.to_i
291
294
  ```
292
295
 
293
- ## How It Works
296
+ ## Include Paths
297
+
298
+ By default, Proscenium will serve files ending with any of these extension: `js,mjs,css,jsx`, and only from `config`, `app/views`, `lib` and `node_modules` directories.
299
+
300
+ However, you can customise these paths with the `include_path` config option...
301
+
302
+ ```ruby
303
+ Rails.application.config.proscenium.include_paths << 'app/components'
304
+ ```
305
+
306
+ ## rjs is back!
294
307
 
295
- Proscenium provides a Rails middleware that proxies requests for your frontend code. By default, it will simply search for a file of the same name in your Rails root. For example, a request for '/app/views/layouts/application.js' or '/lib/hooks.js' will return that exact file relative to your Rails root.
308
+ Proscenium brings back RJS! Any path ending in .rjs will be served from your Rails app. This allows you to import server rendered javascript.
296
309
 
297
- This allows your frontend code to become first class citizens of you Rails application.
310
+ ## Serving from Ruby Gem
311
+
312
+ *docs needed*
298
313
 
299
314
  ## Development
300
315
 
@@ -302,6 +317,12 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
302
317
 
303
318
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
304
319
 
320
+ ### Running Go benchmarks
321
+
322
+ ```bash
323
+ go test ./internal/builder -bench=. -run="^$" -count=10 -benchmem
324
+ ```
325
+
305
326
  ## Contributing
306
327
 
307
328
  Bug reports and pull requests are welcome on GitHub at https://github.com/joelmoss/proscenium. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/joelmoss/proscenium/blob/master/CODE_OF_CONDUCT.md).
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium
4
+ class CssModule::ClassNamesResolver
5
+ def initialize(class_names, phlex_path)
6
+ @class_names = class_names.split
7
+ @stylesheets = {}
8
+ @phlex_path = phlex_path.sub_ext('.module.css')
9
+
10
+ resolve_class_names
11
+ end
12
+
13
+ def class_names
14
+ @class_names.join(' ')
15
+ end
16
+
17
+ def stylesheets
18
+ @stylesheets.map { |_, values| values[:resolved_path] }
19
+ end
20
+
21
+ private
22
+
23
+ def resolve_class_names
24
+ @class_names.map! do |class_name|
25
+ if class_name.include?('/')
26
+ if class_name.starts_with?('@')
27
+ # Scoped bare specifier (eg. "@scoped/package/lib/button@default").
28
+ _, path, name = class_name.split('@')
29
+ path = "@#{path}"
30
+ elsif class_name.starts_with?('/')
31
+ # Local path with leading slash.
32
+ path, name = class_name[1..].split('@')
33
+ else
34
+ # Bare specifier (eg. "mypackage/lib/button@default").
35
+ path, name = class_name.split('@')
36
+ end
37
+
38
+ path += '.module.css'
39
+
40
+ Utils.css_modularise_class_name name, digest: add_stylesheet(path)[:digest]
41
+ elsif class_name.starts_with?('@')
42
+ Utils.css_modularise_class_name class_name[1..],
43
+ digest: add_stylesheet(@phlex_path)[:digest]
44
+ else
45
+ class_name
46
+ end
47
+ end
48
+ end
49
+
50
+ def add_stylesheet(path)
51
+ return @stylesheets[path] if @stylesheets.key?(path)
52
+
53
+ resolved_path = Utils.resolve_path(path.to_s)
54
+
55
+ unless Rails.root.join(resolved_path[1..]).exist?
56
+ raise CssModule::StylesheetNotFound, resolved_path
57
+ end
58
+
59
+ # Note that the digest is based on the resolved (URL) path, not the original path.
60
+ @stylesheets[path] = {
61
+ resolved_path: resolved_path,
62
+ digest: Utils.digest(resolved_path)
63
+ }
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium
4
+ class CssModule::Resolver
5
+ attr_reader :side_loaded_paths
6
+
7
+ # @param path [Pathname] Absolute file system path to the Ruby file that will be side loaded.
8
+ def initialize(path, side_load: true, hash: nil)
9
+ raise ArgumentError, "'#{path}' must be a `Pathname`" unless path.is_a?(Pathname)
10
+
11
+ @path = path
12
+ @hash = hash
13
+ @css_module_path = path.sub_ext('.module.css')
14
+ @side_load = side_load
15
+ @side_loaded_paths = nil
16
+ end
17
+
18
+ # Parses the given `content` for CSS modules names ('class' attributes beginning with '@'), and
19
+ # returns the content with said CSS Modules replaced with the compiled class names.
20
+ #
21
+ # Example:
22
+ # <div class="@my_css_module_name"></div>
23
+ def compile_class_names(content)
24
+ doc = Nokogiri::HTML::DocumentFragment.parse(content)
25
+
26
+ return content if (modules = doc.css('[class*="@"]')).empty?
27
+
28
+ modules.each do |ele|
29
+ classes = ele.classes.map { |cls| cls.starts_with?('@') ? class_names!(cls[1..]) : cls }
30
+ ele['class'] = classes.join(' ')
31
+ end
32
+
33
+ doc.to_html.html_safe
34
+ end
35
+
36
+ # Resolves the given CSS class names to CSS modules. This will also side load the stylesheet if
37
+ # it exists.
38
+ #
39
+ # @param names [String, Array]
40
+ # @returns [Array] of class names generated from the given CSS module `names`.
41
+ def class_names(*names)
42
+ side_load_css_module
43
+ Utils.css_modularise_class_names names, digest: @hash
44
+ end
45
+
46
+ # Like #class_names, but requires that the stylesheet exists.
47
+ #
48
+ # @param names [String, Array]
49
+ # @raises Proscenium::CssModule::NotFound if stylesheet does not exists.
50
+ # @see #class_names
51
+ def class_names!(...)
52
+ raise CssModule::StylesheetNotFound, @css_module_path unless @css_module_path.exist?
53
+
54
+ class_names(...)
55
+ end
56
+
57
+ def side_loaded?
58
+ @side_loaded_paths.present?
59
+ end
60
+
61
+ private
62
+
63
+ def side_load_css_module
64
+ return if !@side_load || !Rails.application.config.proscenium.side_load
65
+
66
+ paths = SideLoad.append @path, { '.module.css' => :css }
67
+
68
+ @side_loaded_paths = if paths.empty?
69
+ nil
70
+ else
71
+ @hash = Utils.digest(paths[0])
72
+ paths
73
+ end
74
+ end
75
+ end
76
+ end
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Proscenium::CssModule
4
- class NotFound < StandardError
3
+ module Proscenium::CssModule
4
+ extend ActiveSupport::Autoload
5
+
6
+ class StylesheetNotFound < StandardError
5
7
  def initialize(pathname)
6
8
  @pathname = pathname
7
9
  super
@@ -12,53 +14,30 @@ class Proscenium::CssModule
12
14
  end
13
15
  end
14
16
 
15
- def initialize(path)
16
- @path = path
17
- @css_module_path = "#{path}.module.css"
18
- end
17
+ autoload :ClassNamesResolver
18
+ autoload :Resolver # deprecated
19
19
 
20
- # Parses the given `content` for CSS modules names ('class' attributes beginning with '@'), and
21
- # returns the content with said CSS Modules replaced with the compiled class names.
20
+ # Like `css_modules`, but will raise if the stylesheet cannot be found.
22
21
  #
23
- # Example:
24
- # <div class="@my_css_module_name"></div>
25
- def compile_class_names(content)
26
- doc = Nokogiri::HTML::DocumentFragment.parse(content)
27
-
28
- return content if (modules = doc.css('[class*="@"]')).empty?
29
-
30
- modules.each do |ele|
31
- classes = ele.classes.map { |cls| cls.starts_with?('@') ? class_names!(cls[1..]) : cls }
32
- ele['class'] = classes.join(' ')
33
- end
34
-
35
- doc.to_html.html_safe
36
- end
37
-
38
- # @returns [Array] of class names generated from the given CSS module `names`.
39
- def class_names(*names)
40
- side_load_css_module
41
- names.flatten.compact.map { |name| "#{name.to_s.camelize(:lower)}#{hash}" }
22
+ # @param name [Array, String]
23
+ def css_module!(names)
24
+ cssm.class_names!(names).join ' '
42
25
  end
43
26
 
44
- # Like #class_names, but requires that the stylesheet exists.
27
+ # Accepts one or more CSS class names, and transforms them into CSS module names.
45
28
  #
46
- # @raises Proscenium::CssModule::NotFound if stylesheet does not exists.
47
- def class_names!(...)
48
- raise NotFound, @css_module_path unless Rails.root.join(@css_module_path).exist?
49
-
50
- class_names(...)
29
+ # @param name [Array, String]
30
+ def css_module(names)
31
+ cssm.class_names(names).join ' '
51
32
  end
52
33
 
53
34
  private
54
35
 
55
- def hash
56
- @hash ||= Digest::SHA1.hexdigest("/#{@css_module_path}")[..7]
36
+ def path
37
+ self.class.path
57
38
  end
58
39
 
59
- def side_load_css_module
60
- return unless Rails.application.config.proscenium.side_load
61
-
62
- Proscenium::SideLoad.append "#{@path}.module", :css
40
+ def cssm
41
+ @cssm ||= Resolver.new(path)
63
42
  end
64
43
  end