proscenium 0.6.0-x86_64-linux → 0.8.2-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 +126 -109
- 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 +108 -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 -77
- 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/parser.js +0 -178
- data/lib/proscenium/compilers/esbuild/import_map/read.js +0 -64
- data/lib/proscenium/compilers/esbuild/import_map/resolver.js +0 -95
- data/lib/proscenium/compilers/esbuild/import_map/utils.js +0 -25
- data/lib/proscenium/compilers/esbuild/resolve_plugin.js +0 -207
- 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 -12
- 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: 0befa74ca37e255e40d47294992f4dde37fd92636b7de4d6bcbd3e7078d26585
|
4
|
+
data.tar.gz: 3ee198704c19fb58cd64ef2b3dfe9ccd76d0b4359507c5d66dfbd9973e0f16f4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b4e97ebe2e7bcd9a85112f3ab3d043472e26a2f8378605c65342cd2c411ec47225955f4b87c8732c749e3a82e400feeb9160cb4ae7cdf41fe8f0d2abc2d2bf63
|
7
|
+
data.tar.gz: 0bacb942fb31e630a47a2a7e5ef6991d05c31a624b53ce4d1aa9ef7027cb94c04241897cfd96e78f0e32ddd17d391a929c482133b216dc437c187837aeae0c0b
|
data/README.md
CHANGED
@@ -1,91 +1,83 @@
|
|
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
|
-
-
|
21
|
-
|
22
|
-
## !! EXPERIMENTAL SOFTWARE !!
|
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.
|
19
|
+
- Source maps.
|
20
|
+
- Phlex and ViewComponent integration.
|
27
21
|
|
28
22
|
## Installation
|
29
23
|
|
30
|
-
Add this line to your application's Gemfile, and you're good to go:
|
24
|
+
Add this line to your Rails application's Gemfile, and you're good to go:
|
31
25
|
|
32
26
|
```ruby
|
33
27
|
gem 'proscenium'
|
34
28
|
```
|
35
29
|
|
36
|
-
|
30
|
+
Please note that Proscenium is designed solely for use with Rails, so will not work - at least out of the box - anywhere else.
|
31
|
+
|
32
|
+
## Client-Side Code Anywhere
|
37
33
|
|
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!
|
34
|
+
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!
|
41
35
|
|
42
|
-
For example, if you have JS that is
|
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`.
|
36
|
+
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`.
|
46
37
|
|
47
|
-
Simply
|
38
|
+
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
39
|
|
49
40
|
Using the examples above...
|
50
41
|
|
51
42
|
- `app/views/users/index.js` => `https://yourapp.com/app/views/users/index.js`
|
52
43
|
- `app/views/layouts/application.css` => `https://yourapp.com/app/views/layouts/application.css`
|
53
44
|
- `lib/utils.js` => `https://yourapp.com/lib/utils.js`
|
45
|
+
- `app/components/menu_component.jsx` => `https://yourapp.com/app/components/menu_component.jsx`
|
54
46
|
- `config/properties.css` => `https://yourapp.com/config/properties.css`
|
55
47
|
|
56
48
|
## Importing
|
57
49
|
|
58
|
-
Proscenium supports importing JS and CSS from
|
50
|
+
Proscenium supports importing JS, JSX, TS and CSS from NPM, by URL, your local app, and even from Ruby Gems.
|
59
51
|
|
60
|
-
|
61
|
-
|
62
|
-
extension.
|
52
|
+
Imported files are bundled together in real time. So no build step or pre-compilation is needed.
|
53
|
+
|
54
|
+
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
55
|
|
64
56
|
### URL Imports
|
65
57
|
|
66
|
-
Any import beginning with `http://` or `https://` will be fetched from the URL provided:
|
58
|
+
Any import beginning with `http://` or `https://` will be fetched from the URL provided. For example:
|
67
59
|
|
68
60
|
```js
|
69
|
-
import React from 'https://esm.sh/react
|
61
|
+
import React from 'https://esm.sh/react'
|
70
62
|
```
|
71
63
|
|
72
64
|
```css
|
73
65
|
@import 'https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css';
|
74
66
|
```
|
75
67
|
|
68
|
+
URL imports are cached, so that each import is only fetched once per server restart.
|
69
|
+
|
76
70
|
### Import from NPM (`node_modules`)
|
77
71
|
|
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):
|
72
|
+
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
73
|
|
81
74
|
```js
|
82
|
-
import React from 'react
|
75
|
+
import React from 'react'
|
83
76
|
```
|
84
77
|
|
85
78
|
### Local Imports
|
86
79
|
|
87
|
-
And of course you can import your own code, using relative or absolute paths (file extension is
|
88
|
-
optional):
|
80
|
+
And of course you can import your own code, using relative or absolute paths (file extension is optional):
|
89
81
|
|
90
82
|
```js /app/views/layouts/application.js
|
91
83
|
import utils from '/lib/utils'
|
@@ -95,32 +87,11 @@ import utils from '/lib/utils'
|
|
95
87
|
import constants from './constants'
|
96
88
|
```
|
97
89
|
|
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.
|
90
|
+
## Import Map [WIP]
|
112
91
|
|
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.
|
92
|
+
[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
93
|
|
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. This is because the import map is parsed and resolved by Proscenium on the
|
121
|
-
server.
|
122
|
-
|
123
|
-
Just create `config/import_map.json`:
|
94
|
+
Just create `config/import_map.json` and specify the imports you want to use. For example:
|
124
95
|
|
125
96
|
```json
|
126
97
|
{
|
@@ -148,9 +119,7 @@ and for CSS...
|
|
148
119
|
@import '@radix-ui/colors/blue.css';
|
149
120
|
```
|
150
121
|
|
151
|
-
You can
|
152
|
-
`config/import_map.json`, create `config/import_map.js`, and define an anonymous function. This
|
153
|
-
function accepts a single `environment` argument.
|
122
|
+
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
123
|
|
155
124
|
```js
|
156
125
|
env => ({
|
@@ -160,18 +129,6 @@ env => ({
|
|
160
129
|
})
|
161
130
|
```
|
162
131
|
|
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
132
|
## Side Loading
|
176
133
|
|
177
134
|
Proscenium has built in support for automatically side loading JS and CSS with your views and
|
@@ -203,6 +160,8 @@ to `false`.
|
|
203
160
|
|
204
161
|
## CSS Modules
|
205
162
|
|
163
|
+
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.
|
164
|
+
|
206
165
|
Give any CSS file a `.module.css` extension, and Proscenium will load it as a CSS Module...
|
207
166
|
|
208
167
|
```css
|
@@ -211,7 +170,7 @@ Give any CSS file a `.module.css` extension, and Proscenium will load it as a CS
|
|
211
170
|
}
|
212
171
|
```
|
213
172
|
|
214
|
-
The above produces:
|
173
|
+
The above input produces:
|
215
174
|
|
216
175
|
```css
|
217
176
|
.header5564cdbb {
|
@@ -219,63 +178,104 @@ The above produces:
|
|
219
178
|
}
|
220
179
|
```
|
221
180
|
|
222
|
-
Importing a CSS file from JS will automatically append the stylesheet to the document's head.
|
223
|
-
results of the import will be an object of CSS modules.
|
181
|
+
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
182
|
|
225
183
|
```js
|
226
184
|
import styles from './styles.module.css'
|
185
|
+
// styles == { header: 'header5564cdbb' }
|
227
186
|
```
|
228
187
|
|
229
|
-
|
188
|
+
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
189
|
|
231
|
-
|
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:
|
190
|
+
Also, importing a CSS module from another CSS module will result in the same digest string for all classes.
|
234
191
|
|
235
|
-
|
236
|
-
mount Proscenium::Railtie, at: '/proscenium' if Rails.env.development?
|
237
|
-
```
|
192
|
+
## CSS Mixins
|
238
193
|
|
239
|
-
|
194
|
+
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
195
|
|
241
|
-
|
196
|
+
CSS mixins are supported using the `@define-mixin` and `@mixin` at-rules.
|
242
197
|
|
243
|
-
|
198
|
+
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
199
|
|
245
|
-
|
200
|
+
```css
|
201
|
+
// /lib/mixins.css
|
202
|
+
@define-mixin bigText {
|
203
|
+
font-size: 50px;
|
204
|
+
}
|
205
|
+
```
|
246
206
|
|
247
|
-
|
207
|
+
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
208
|
|
249
|
-
|
209
|
+
```css
|
210
|
+
// /app/views/layouts/application.css
|
211
|
+
p {
|
212
|
+
@mixin bigText from url('/lib/mixins.css');
|
213
|
+
color: red;
|
214
|
+
}
|
215
|
+
```
|
250
216
|
|
251
|
-
|
217
|
+
The above produce this output:
|
252
218
|
|
253
219
|
```css
|
254
|
-
|
255
|
-
@define-mixin bigText {
|
220
|
+
p {
|
256
221
|
font-size: 50px;
|
222
|
+
color: red;
|
257
223
|
}
|
258
224
|
```
|
259
225
|
|
226
|
+
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.
|
227
|
+
|
260
228
|
```css
|
261
|
-
|
229
|
+
@define-mixin bigText {
|
230
|
+
font-size: 50px;
|
231
|
+
}
|
232
|
+
|
262
233
|
p {
|
263
234
|
@mixin bigText;
|
264
235
|
color: red;
|
265
236
|
}
|
266
237
|
```
|
267
238
|
|
268
|
-
|
239
|
+
CSS modules and Mixins works perfectly together. You can include a mixin in a CSS module.
|
240
|
+
|
241
|
+
## Importing SVG from JS(X)
|
269
242
|
|
270
|
-
|
271
|
-
|
272
|
-
|
243
|
+
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.
|
244
|
+
|
245
|
+
## Environment Variables
|
246
|
+
|
247
|
+
Import any environment variables into your JS(X) code.
|
248
|
+
|
249
|
+
```js
|
250
|
+
import RAILS_ENV from '@proscenium/env/RAILS_ENV'
|
251
|
+
```
|
252
|
+
|
253
|
+
You can only access environment variables that are explicitly named. It will export `undefined` if the env variable does not exist.
|
254
|
+
|
255
|
+
## Importing i18n
|
256
|
+
|
257
|
+
Basic support is provided for importing your Rails locale files from `config/locales/*.yml`, exporting them as JSON.
|
258
|
+
|
259
|
+
```js
|
260
|
+
import translations from '@proscenium/i18n'
|
261
|
+
// translations.en.*
|
262
|
+
```
|
263
|
+
|
264
|
+
## Phlex Support
|
265
|
+
|
266
|
+
*docs needed*
|
267
|
+
|
268
|
+
## ViewComponent Support
|
269
|
+
|
270
|
+
*docs needed*
|
271
|
+
|
272
|
+
## Cache Busting [*COMING SOON*]
|
273
|
+
|
274
|
+
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
275
|
|
274
276
|
For example, if you set `REVISION=v1`, URL's will be appended with `?v1`: `/my/imported/file.js?v1`.
|
275
277
|
|
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.
|
278
|
+
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
279
|
|
280
280
|
You can set the `cache_query_string` config option directly to define any query string you wish:
|
281
281
|
|
@@ -283,18 +283,29 @@ You can set the `cache_query_string` config option directly to define any query
|
|
283
283
|
Rails.application.config.proscenium.cache_query_string = 'my-cache-busting-version-string'
|
284
284
|
```
|
285
285
|
|
286
|
-
The cache is set with a `max-age` of 30 days. You can customise this with the `cache_max_age` config
|
287
|
-
option:
|
286
|
+
The cache is set with a `max-age` of 30 days. You can customise this with the `cache_max_age` config option:
|
288
287
|
|
289
288
|
```ruby
|
290
289
|
Rails.application.config.proscenium.cache_max_age = 12.months.to_i
|
291
290
|
```
|
292
291
|
|
293
|
-
##
|
292
|
+
## Include Paths
|
293
|
+
|
294
|
+
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.
|
295
|
+
|
296
|
+
However, you can customise these paths with the `include_path` config option...
|
297
|
+
|
298
|
+
```ruby
|
299
|
+
Rails.application.config.proscenium.include_paths << 'app/components'
|
300
|
+
```
|
301
|
+
|
302
|
+
## rjs is back!
|
294
303
|
|
295
|
-
Proscenium
|
304
|
+
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
305
|
|
297
|
-
|
306
|
+
## Serving from Ruby Gem
|
307
|
+
|
308
|
+
*docs needed*
|
298
309
|
|
299
310
|
## Development
|
300
311
|
|
@@ -302,6 +313,12 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
302
313
|
|
303
314
|
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
315
|
|
316
|
+
### Running Go benchmarks
|
317
|
+
|
318
|
+
```bash
|
319
|
+
go test ./internal/builder -bench=. -run="^$" -count=10 -benchmem
|
320
|
+
```
|
321
|
+
|
305
322
|
## Contributing
|
306
323
|
|
307
324
|
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
|