proscenium 0.5.1-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.
- checksums.yaml +4 -4
- data/README.md +128 -92
- data/lib/proscenium/css_module/class_names_resolver.rb +66 -0
- data/lib/proscenium/css_module/resolver.rb +76 -0
- data/lib/proscenium/css_module.rb +18 -39
- data/lib/proscenium/esbuild/golib.rb +97 -0
- data/lib/proscenium/esbuild.rb +32 -0
- data/lib/proscenium/ext/proscenium +0 -0
- data/lib/proscenium/ext/proscenium.h +109 -0
- data/lib/proscenium/helper.rb +0 -23
- data/lib/proscenium/log_subscriber.rb +26 -0
- data/lib/proscenium/middleware/base.rb +28 -36
- data/lib/proscenium/middleware/esbuild.rb +18 -44
- data/lib/proscenium/middleware/url.rb +1 -6
- data/lib/proscenium/middleware.rb +12 -16
- data/lib/proscenium/phlex/component_concerns.rb +27 -0
- data/lib/proscenium/phlex/page.rb +62 -0
- data/lib/proscenium/phlex/react_component.rb +52 -8
- data/lib/proscenium/phlex/resolve_css_modules.rb +67 -0
- data/lib/proscenium/phlex.rb +34 -33
- data/lib/proscenium/railtie.rb +41 -67
- data/lib/proscenium/side_load/ensure_loaded.rb +25 -0
- data/lib/proscenium/side_load/helper.rb +25 -0
- data/lib/proscenium/side_load/monkey.rb +48 -0
- data/lib/proscenium/side_load.rb +58 -52
- data/lib/proscenium/version.rb +1 -1
- data/lib/proscenium/view_component/react_component.rb +14 -0
- data/lib/proscenium/view_component.rb +28 -18
- data/lib/proscenium.rb +79 -2
- metadata +35 -74
- data/app/channels/proscenium/connection.rb +0 -13
- data/app/channels/proscenium/reload_channel.rb +0 -9
- data/bin/esbuild +0 -0
- data/bin/lightningcss +0 -0
- data/config/routes.rb +0 -7
- data/lib/proscenium/compiler.js +0 -84
- data/lib/proscenium/compilers/esbuild/argument_error.js +0 -24
- data/lib/proscenium/compilers/esbuild/compile_error.js +0 -148
- data/lib/proscenium/compilers/esbuild/css/postcss.js +0 -67
- data/lib/proscenium/compilers/esbuild/css_plugin.js +0 -172
- data/lib/proscenium/compilers/esbuild/env_plugin.js +0 -46
- data/lib/proscenium/compilers/esbuild/http_bundle_plugin.js +0 -53
- data/lib/proscenium/compilers/esbuild/import_map.js +0 -59
- data/lib/proscenium/compilers/esbuild/resolve_plugin.js +0 -205
- data/lib/proscenium/compilers/esbuild/setup_plugin.js +0 -45
- data/lib/proscenium/compilers/esbuild/solidjs_plugin.js +0 -24
- data/lib/proscenium/compilers/esbuild.bench.js +0 -14
- data/lib/proscenium/compilers/esbuild.js +0 -179
- data/lib/proscenium/link_to_helper.rb +0 -40
- data/lib/proscenium/middleware/lightningcss.rb +0 -64
- data/lib/proscenium/middleware/outside_root.rb +0 -26
- data/lib/proscenium/middleware/runtime.rb +0 -22
- data/lib/proscenium/middleware/static.rb +0 -14
- data/lib/proscenium/phlex/component.rb +0 -9
- data/lib/proscenium/precompile.rb +0 -31
- data/lib/proscenium/runtime/auto_reload.js +0 -40
- data/lib/proscenium/runtime/react_shim/index.js +0 -1
- data/lib/proscenium/runtime/react_shim/package.json +0 -5
- data/lib/proscenium/utils.js +0 -8
- data/lib/tasks/assets.rake +0 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2c170e7b81f5a5e8591e8876e9b25cc25c0815cd453b2b14cb840229c471fdc2
|
4
|
+
data.tar.gz: 75df6068a461a45bd4cdf82b382096a25c6c79b934d27f425ac2ba1e97384b8e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
5
|
-
configuration
|
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
|
-
-
|
9
|
-
-
|
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
|
14
|
-
-
|
15
|
-
-
|
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
|
-
-
|
20
|
-
-
|
19
|
+
- Source maps.
|
20
|
+
- Phlex and ViewComponent integration.
|
21
21
|
|
22
|
-
##
|
22
|
+
## ⚠️ WORK IN PROGRESS ⚠️
|
23
23
|
|
24
|
-
While my goal is to use Proscenium in production, I
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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,29 +91,11 @@ import utils from '/lib/utils'
|
|
95
91
|
import constants from './constants'
|
96
92
|
```
|
97
93
|
|
98
|
-
##
|
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.
|
94
|
+
## Import Map [WIP]
|
112
95
|
|
113
|
-
|
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.
|
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.
|
116
97
|
|
117
|
-
|
118
|
-
|
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. Just create `config/import_map.json`:
|
98
|
+
Just create `config/import_map.json` and specify the imports you want to use. For example:
|
121
99
|
|
122
100
|
```json
|
123
101
|
{
|
@@ -145,9 +123,7 @@ and for CSS...
|
|
145
123
|
@import '@radix-ui/colors/blue.css';
|
146
124
|
```
|
147
125
|
|
148
|
-
You can
|
149
|
-
`config/import_map.json`, create `config/import_map.js`, and define an anonymous function. This
|
150
|
-
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.
|
151
127
|
|
152
128
|
```js
|
153
129
|
env => ({
|
@@ -188,6 +164,8 @@ to `false`.
|
|
188
164
|
|
189
165
|
## CSS Modules
|
190
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
|
+
|
191
169
|
Give any CSS file a `.module.css` extension, and Proscenium will load it as a CSS Module...
|
192
170
|
|
193
171
|
```css
|
@@ -196,7 +174,7 @@ Give any CSS file a `.module.css` extension, and Proscenium will load it as a CS
|
|
196
174
|
}
|
197
175
|
```
|
198
176
|
|
199
|
-
The above produces:
|
177
|
+
The above input produces:
|
200
178
|
|
201
179
|
```css
|
202
180
|
.header5564cdbb {
|
@@ -204,63 +182,104 @@ The above produces:
|
|
204
182
|
}
|
205
183
|
```
|
206
184
|
|
207
|
-
Importing a CSS file from JS will automatically append the stylesheet to the document's head.
|
208
|
-
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.
|
209
186
|
|
210
187
|
```js
|
211
188
|
import styles from './styles.module.css'
|
189
|
+
// styles == { header: 'header5564cdbb' }
|
212
190
|
```
|
213
191
|
|
214
|
-
|
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.
|
215
193
|
|
216
|
-
|
217
|
-
the page when any files changes. It is enabled by default in development, and requires that you
|
218
|
-
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.
|
219
195
|
|
220
|
-
|
221
|
-
mount Proscenium::Railtie, at: '/proscenium' if Rails.env.development?
|
222
|
-
```
|
196
|
+
## CSS Mixins
|
223
197
|
|
224
|
-
|
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.
|
225
199
|
|
226
|
-
|
200
|
+
CSS mixins are supported using the `@define-mixin` and `@mixin` at-rules.
|
227
201
|
|
228
|
-
|
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:
|
229
203
|
|
230
|
-
|
204
|
+
```css
|
205
|
+
// /lib/mixins.css
|
206
|
+
@define-mixin bigText {
|
207
|
+
font-size: 50px;
|
208
|
+
}
|
209
|
+
```
|
231
210
|
|
232
|
-
|
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.
|
233
212
|
|
234
|
-
|
213
|
+
```css
|
214
|
+
// /app/views/layouts/application.css
|
215
|
+
p {
|
216
|
+
@mixin bigText from url('/lib/mixins.css');
|
217
|
+
color: red;
|
218
|
+
}
|
219
|
+
```
|
235
220
|
|
236
|
-
|
221
|
+
The above produce this output:
|
237
222
|
|
238
223
|
```css
|
239
|
-
|
240
|
-
@define-mixin bigText {
|
224
|
+
p {
|
241
225
|
font-size: 50px;
|
226
|
+
color: red;
|
242
227
|
}
|
243
228
|
```
|
244
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
|
+
|
245
232
|
```css
|
246
|
-
|
233
|
+
@define-mixin bigText {
|
234
|
+
font-size: 50px;
|
235
|
+
}
|
236
|
+
|
247
237
|
p {
|
248
238
|
@mixin bigText;
|
249
239
|
color: red;
|
250
240
|
}
|
251
241
|
```
|
252
242
|
|
253
|
-
|
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
|
+
```
|
254
267
|
|
255
|
-
|
256
|
-
|
257
|
-
|
268
|
+
## Phlex Support
|
269
|
+
|
270
|
+
*docs needed*
|
271
|
+
|
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.
|
258
279
|
|
259
280
|
For example, if you set `REVISION=v1`, URL's will be appended with `?v1`: `/my/imported/file.js?v1`.
|
260
281
|
|
261
|
-
It is assumed that the `REVISION` env var will be unique between deploys. If it isn't, then assets
|
262
|
-
will continue to be cached as the same version between deploys. I recommend you assign a version
|
263
|
-
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.
|
264
283
|
|
265
284
|
You can set the `cache_query_string` config option directly to define any query string you wish:
|
266
285
|
|
@@ -268,18 +287,29 @@ You can set the `cache_query_string` config option directly to define any query
|
|
268
287
|
Rails.application.config.proscenium.cache_query_string = 'my-cache-busting-version-string'
|
269
288
|
```
|
270
289
|
|
271
|
-
The cache is set with a `max-age` of 30 days. You can customise this with the `cache_max_age` config
|
272
|
-
option:
|
290
|
+
The cache is set with a `max-age` of 30 days. You can customise this with the `cache_max_age` config option:
|
273
291
|
|
274
292
|
```ruby
|
275
293
|
Rails.application.config.proscenium.cache_max_age = 12.months.to_i
|
276
294
|
```
|
277
295
|
|
278
|
-
##
|
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.
|
279
299
|
|
280
|
-
|
300
|
+
However, you can customise these paths with the `include_path` config option...
|
281
301
|
|
282
|
-
|
302
|
+
```ruby
|
303
|
+
Rails.application.config.proscenium.include_paths << 'app/components'
|
304
|
+
```
|
305
|
+
|
306
|
+
## rjs is back!
|
307
|
+
|
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.
|
309
|
+
|
310
|
+
## Serving from Ruby Gem
|
311
|
+
|
312
|
+
*docs needed*
|
283
313
|
|
284
314
|
## Development
|
285
315
|
|
@@ -287,6 +317,12 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
287
317
|
|
288
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).
|
289
319
|
|
320
|
+
### Running Go benchmarks
|
321
|
+
|
322
|
+
```bash
|
323
|
+
go test ./internal/builder -bench=. -run="^$" -count=10 -benchmem
|
324
|
+
```
|
325
|
+
|
290
326
|
## Contributing
|
291
327
|
|
292
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
|
-
|
4
|
-
|
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
|
-
|
16
|
-
|
17
|
-
@css_module_path = "#{path}.module.css"
|
18
|
-
end
|
17
|
+
autoload :ClassNamesResolver
|
18
|
+
autoload :Resolver # deprecated
|
19
19
|
|
20
|
-
#
|
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
|
-
#
|
24
|
-
|
25
|
-
|
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
|
-
#
|
27
|
+
# Accepts one or more CSS class names, and transforms them into CSS module names.
|
45
28
|
#
|
46
|
-
# @
|
47
|
-
def
|
48
|
-
|
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
|
56
|
-
|
36
|
+
def path
|
37
|
+
self.class.path
|
57
38
|
end
|
58
39
|
|
59
|
-
def
|
60
|
-
|
61
|
-
|
62
|
-
Proscenium::SideLoad.append "#{@path}.module", :css
|
40
|
+
def cssm
|
41
|
+
@cssm ||= Resolver.new(path)
|
63
42
|
end
|
64
43
|
end
|