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.
- checksums.yaml +4 -4
- data/README.md +128 -107
- 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 -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: 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,32 +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.
|
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
|
-
|
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
|
-
|
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
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
236
|
-
mount Proscenium::Railtie, at: '/proscenium' if Rails.env.development?
|
237
|
-
```
|
196
|
+
## CSS Mixins
|
238
197
|
|
239
|
-
|
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
|
-
|
200
|
+
CSS mixins are supported using the `@define-mixin` and `@mixin` at-rules.
|
242
201
|
|
243
|
-
|
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
|
-
|
204
|
+
```css
|
205
|
+
// /lib/mixins.css
|
206
|
+
@define-mixin bigText {
|
207
|
+
font-size: 50px;
|
208
|
+
}
|
209
|
+
```
|
246
210
|
|
247
|
-
|
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
|
-
|
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
|
-
|
221
|
+
The above produce this output:
|
252
222
|
|
253
223
|
```css
|
254
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
271
|
-
|
272
|
-
|
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
|
-
##
|
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
|
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
|
-
|
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
|
-
|
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
|