proscenium 0.10.0-x86_64-linux → 0.11.0.pre.1-x86_64-linux

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +175 -39
  3. data/lib/proscenium/builder.rb +14 -10
  4. data/lib/proscenium/css_module/path.rb +31 -0
  5. data/lib/proscenium/css_module/transformer.rb +76 -0
  6. data/lib/proscenium/css_module.rb +6 -28
  7. data/lib/proscenium/ensure_loaded.rb +27 -0
  8. data/lib/proscenium/ext/proscenium +0 -0
  9. data/lib/proscenium/helper.rb +62 -0
  10. data/lib/proscenium/importer.rb +110 -0
  11. data/lib/proscenium/libs/react-manager/index.jsx +88 -0
  12. data/lib/proscenium/libs/react-manager/react.js +2 -0
  13. data/lib/proscenium/middleware/esbuild.rb +2 -4
  14. data/lib/proscenium/middleware.rb +2 -1
  15. data/lib/proscenium/{side_load/monkey.rb → monkey.rb} +11 -15
  16. data/lib/proscenium/phlex/{resolve_css_modules.rb → css_modules.rb} +6 -20
  17. data/lib/proscenium/phlex/page.rb +2 -2
  18. data/lib/proscenium/phlex/react_component.rb +26 -27
  19. data/lib/proscenium/phlex.rb +10 -29
  20. data/lib/proscenium/railtie.rb +14 -26
  21. data/lib/proscenium/react_componentable.rb +94 -0
  22. data/lib/proscenium/resolver.rb +37 -0
  23. data/lib/proscenium/side_load.rb +13 -73
  24. data/lib/proscenium/source_path.rb +15 -0
  25. data/lib/proscenium/utils.rb +13 -0
  26. data/lib/proscenium/version.rb +1 -1
  27. data/lib/proscenium/view_component/css_modules.rb +11 -0
  28. data/lib/proscenium/view_component/react_component.rb +15 -15
  29. data/lib/proscenium/view_component/sideload.rb +4 -0
  30. data/lib/proscenium/view_component.rb +8 -38
  31. data/lib/proscenium.rb +23 -68
  32. metadata +19 -29
  33. data/lib/proscenium/componentable.rb +0 -63
  34. data/lib/proscenium/css_module/class_names_resolver.rb +0 -66
  35. data/lib/proscenium/css_module/resolver.rb +0 -76
  36. data/lib/proscenium/current.rb +0 -9
  37. data/lib/proscenium/phlex/component_concerns.rb +0 -9
  38. data/lib/proscenium/side_load/ensure_loaded.rb +0 -25
  39. data/lib/proscenium/side_load/helper.rb +0 -41
  40. data/lib/proscenium/view_component/tag_builder.rb +0 -23
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 56b426c6cecb6a9f951863c43cf2481da79a3dd9c8e68178e5af574819d43670
4
- data.tar.gz: 1c51042422b33d92432ea0620e7eed761499dfc217b9fc6f3e87038b83201e76
3
+ metadata.gz: 80efacad8425cf4168501954ad90dc4329ef6989b9ca583403070719e734f93b
4
+ data.tar.gz: 726aa7aaa0a4b169c90f9ad1b014afc9ac39aeb9c56a93d7f3b75e8adeb69198
5
5
  SHA512:
6
- metadata.gz: 2eb6f29a59761899c12e98ce25fad39109aef3878a7afa053851a6387433108ff8a79948b2fd083c8530ef9da83ed8c60fc02851df442bbe517f6663beb6dc03
7
- data.tar.gz: ef7fd29013ec3ce60044a315d17b0cdb2aa738aefe39b16273abbf9636773f0048a9c1cf9d6d8570b28f5609bed4de442e2697106bf90c61c993a9266317a618
6
+ metadata.gz: f90b3f793838132104e3b9b563f8299facf614af5dd40b66fdde367314f60450ed852607d1ec06176ee852679d9183a7bf9ab23cf6c6ecadff80450391ee14a9
7
+ data.tar.gz: 1a8d955df5a5c129793a4b02792756a2c5ca2c6073633b6c5fb71ed43af3abac7881ded5413d75ce3b1537a3920c2219bce1e32b78cd9d4b1d74f70073be66ff
data/README.md CHANGED
@@ -1,18 +1,16 @@
1
1
  # Proscenium - Modern client-side development for Rails
2
2
 
3
- Proscenium treats your client-side code as first class citizens of your Rails app, and assumes a
4
- "fast by default" internet. It bundles your JS, JSX and CSS in real time, on demand, and with zero
5
- configuration.
3
+ Proscenium treats your client-side code as first class citizens of your Rails app, and assumes a "fast by default" internet. It bundles your JavaScript and CSS in real time, on demand, and with zero configuration.
6
4
 
7
- - Fast real-time bundling, tree-shaking and minification of Javascript (.js,.jsx), Typescript (.ts,.tsx) and CSS (.css).
5
+ **The highlights:**
6
+
7
+ - Fast real-time bundling, tree-shaking, code-splitting and minification of Javascript (.js,.jsx), Typescript (.ts,.tsx) and CSS (.css).
8
8
  - NO JavaScript runtime needed - just the browser!
9
9
  - NO build step or pre-compilation.
10
10
  - NO additional process or server - Just run Rails!
11
11
  - Deep integration with Rails.
12
- - Zero configuration.
13
- - Serve assets from anywhere within your Rails root (/app, /config, /lib, etc.).
14
- - Automatically side load JS/TS/CSS for your layouts and views.
15
- - ESM importing from NPM, URLs, and locally.
12
+ - Automatically side-load your layouts, views, and partials.
13
+ - Import from NPM, URL's, and locally.
16
14
  - Server-side import map support.
17
15
  - CSS Modules & mixins.
18
16
  - Source maps.
@@ -54,7 +52,7 @@ configuration.
54
52
 
55
53
  ## Getting Started
56
54
 
57
- Getting started obviously depends on whether you are adding Proscenium to an existing Rails app, or creating a new Rails app. So please choose the appropriate guide below:
55
+ Getting started obviously depends on whether you are adding Proscenium to an existing Rails app, or creating a new one. So choose the appropriate guide below:
58
56
 
59
57
  - [Getting Started with a new Rails app](https://github.com/joelmoss/proscenium/blob/master/docs/guides/new_rails_app.md)
60
58
  - Getting Started with an existing Rails app
@@ -91,7 +89,7 @@ Using the examples above...
91
89
 
92
90
  ## Side Loading
93
91
 
94
- > Prior to **0.10.0**, only assets with the extension `.js`, `.ts` and `.css` were side loaded. From 0.10.0, all assets are side loaded, including `.jsx`, and `.tsx`. Also partials were not side loaded prior to 0.10.0.
92
+ > Prior to **0.10.0**, only assets with the extension `.js`, `.ts` and `.css` were side loaded. From 0.10.0, all assets are side loaded, including `.jsx`, `.tsx`, and `.module.css`. Also partials were not side loaded prior to 0.10.0.
95
93
 
96
94
  Proscenium is best experienced when you side load your assets.
97
95
 
@@ -101,7 +99,7 @@ With Rails you would typically declaratively load your JavaScript and CSS assets
101
99
 
102
100
  For example, you may have top-level "application" CSS located in a file at `/app/assets/application.css`. Likewise, you may have some global JavaScript located in a file at `/app/assets/application.js`.
103
101
 
104
- You would include those two files in your application layout, something like this:
102
+ You would manually and declaratively include those two files in your application layout, something like this:
105
103
 
106
104
  ```erb
107
105
  <%# /app/views/layouts/application.html.erb %>
@@ -119,7 +117,7 @@ You would include those two files in your application layout, something like thi
119
117
  </html>
120
118
  ```
121
119
 
122
- Now, you may have some CSS and JavaScript that is only required by a specific view and partial, so you would load that in your view, something like this:
120
+ Now, you may have some CSS and JavaScript that is only required by a specific view and partial, so you would load that in your view (or layout), something like this:
123
121
 
124
122
  ```erb
125
123
  <%# /app/views/users/index.html.erb %>
@@ -163,25 +161,25 @@ Your application layout is at `/app/views/layouts/application.hml.erb`, and the
163
161
  - `/app/views/users/index.js`
164
162
  - `/app/views/users/_user.js` (partial)
165
163
 
166
- Now, in your layout and view, replace the `javascript_include_tag` and `stylesheet_link_tag` helpers with the `side_load_stylesheets` and `side_load_javascripts` helpers from Proscenium. Something like this:
164
+ Now, in your layout and view, replace the `javascript_include_tag` and `stylesheet_link_tag` helpers with the `include_stylesheets` and `include_javascripts` helpers from Proscenium. Something like this:
167
165
 
168
166
  ```erb
169
167
  <!DOCTYPE html>
170
168
  <html>
171
169
  <head>
172
170
  <title>Hello World</title>
173
- <%= side_load_stylesheets %>
171
+ <%= include_stylesheets %>
174
172
  </head>
175
173
  <body>
176
174
  <%= yield %>
177
- <%= side_load_javascripts type: 'module', defer: true %>
175
+ <%= include_javascripts type: 'module', defer: true %>
178
176
  </body>
179
177
  </html>
180
178
  ```
181
179
 
182
180
  > NOTE that Proscenium is desiged to work with modern JavaAscript, and assumes [ESModules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) are used everywhere. This is why the `type` attribute is set to `module` in the example above. If you are not using ESModules, then you can omit the `type` attribute.
183
181
 
184
- On each page request, Proscenium will check if your views, layouts and partials have a JS/TS/CSS file of the same name, and then include them wherever your placed the `side_load_stylesheets` and `side_load_javascripts` helpers.
182
+ On each page request, Proscenium will check if your views, layouts and partials have a JS/TS/CSS file of the same name, and then include them wherever your placed the `include_stylesheets` and `include_javascripts` helpers.
185
183
 
186
184
  Now you never have to remember to include your assets again. Just create them alongside your views, partials and layouts, and Proscenium will take care of the rest.
187
185
 
@@ -397,14 +395,6 @@ one();
397
395
 
398
396
  > Available in `>=0.10.0`.
399
397
 
400
- > #### *Experimental!* 🧪
401
- >
402
- > Code splitting is currently experimentally and limited to side loaded code. It is disabled by default. You can enable code splitting by setting the `code_splitting` configuration option to `true` in your application's `/config/application.rb`:
403
- >
404
- > ```ruby
405
- > config.proscenium.code_splitting = true
406
- > ```
407
-
408
398
  [Side loaded](#side-loading) assets are automatically code split. This means that if you have a file that is imported and used imported several times, and by different files, it will be split off into a separate file.
409
399
 
410
400
  As an example:
@@ -438,6 +428,11 @@ If these files are side loaded, then `father.js` will be split off into a separa
438
428
 
439
429
  - Without code splitting, an import() expression becomes `Promise.resolve().then(() => require())` instead. This still preserves the asynchronous semantics of the expression but it means the imported code is included in the same bundle instead of being split off into a separate file.
440
430
 
431
+ Code splitting is enabled by default. You can disable it by setting the `code_splitting` configuration option to `false` in your application's `/config/application.rb`:
432
+ ```ruby
433
+ config.proscenium.code_splitting = false
434
+ ```
435
+
441
436
  ### JavaScript Caveats
442
437
 
443
438
  There are a few important caveats as far as JavaScript is concerned. These are [detailed on the esbuild site](https://esbuild.github.io/content-types/#javascript-caveats).
@@ -464,34 +459,74 @@ export let Button = ({ text }) => {
464
459
 
465
460
  ### CSS Modules
466
461
 
467
- 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.
462
+ 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 will work everywhere, even in plain CSS files.)
468
463
 
469
- Give any CSS file a `.module.css` extension, and Proscenium will load it as a CSS Module...
464
+ Give any CSS file a `.module.css` extension, and Proscenium will treat it as a CSS Module, transforming all class names with a suffix unique to the file.
470
465
 
471
466
  ```css
472
- .header {
473
- background-color: #00f;
467
+ .title {
468
+ font-size: 20em;
474
469
  }
475
470
  ```
476
471
 
477
472
  The above input produces:
478
473
 
479
474
  ```css
480
- .header5564cdbb {
481
- background-color: #00f;
475
+ .title-5564cdbb {
476
+ font-size: 20em;
482
477
  }
483
478
  ```
484
479
 
485
- 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.
480
+ You now have a unique class name that you can use pretty much anywhere.
481
+
482
+ #### In your Views
483
+
484
+ You can reference CSS modules from your Rails views, partials, and layouts using the `css_module` helper, which accepts one or more class names, and will return the equivilent CSS module names - the class name with the unique suffix appended.
485
+
486
+ With [side-loading](#side-loading) setup, you can use the `css_module` helper as follows.
487
+
488
+ ```erb
489
+ <div>
490
+ <h1 class="<%= css_module :hello_title %>">Hello World</h1>
491
+ <p class="<%= css_module :body, paragraph: %>">
492
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
493
+ </p>
494
+ </div>
495
+ ```
496
+
497
+ `css_module` accepts multiple class names, and will return a space-separated string of transformed CSS module names.
498
+
499
+ ```ruby
500
+ css_module :my_module_name
501
+ # => "my_module_name-ABCD1234"
502
+ ```
503
+
504
+ You can even reference a class from any CSS file by passing the URL path to the file, as a prefix to the class name. Doing so will automatically [side load](#side-loading) the stylesheet.
505
+
506
+ ```ruby
507
+ css_module '/app/components/button.css@big_button'
508
+ # => "big_button"
509
+ ```
510
+
511
+ It also supports NPM packages (already installed in /node_modules):
512
+
513
+ ```ruby
514
+ css_module 'mypackage/button@big_button'
515
+ # => "big_button"
516
+ ```
517
+
518
+ #### In your JavaScript
519
+
520
+ Importing a CSS module from JS will automatically append the stylesheet to the document's head. And the result of the import will be an object of CSS class to module names.
486
521
 
487
522
  ```js
488
523
  import styles from './styles.module.css'
489
- // styles == { header: 'header5564cdbb' }
524
+ // styles == { header: 'header-5564cdbb' }
490
525
  ```
491
526
 
492
- 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.
527
+ It is important to note that the exported object of CSS module names is actually a JavaScript [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) object. So destructuring the object will not work. Instead, you must access the properties directly.
493
528
 
494
- Also, importing a CSS module from another CSS module will result in the same digest string for all classes.
529
+ Also, importing a CSS module into another CSS module will result in the same digest string for all classes.
495
530
 
496
531
  ### CSS Mixins
497
532
 
@@ -589,11 +624,112 @@ console.log(version)
589
624
 
590
625
  ## Phlex Support
591
626
 
592
- > *docs needed*
627
+ [Phlex](https://www.phlex.fun/) is a framework for building fast, reusable, testable views in pure Ruby. Proscenium works perfectly with Phlex, with support for side-loading, CSS modules, and more. Simply write your Phlex classes and inherit from `Proscenium::Phlex`.
628
+
629
+ ```ruby
630
+ class MyView < Proscenium::Phlex
631
+ def template
632
+ h1 { 'Hello World' }
633
+ end
634
+ end
635
+ ```
636
+
637
+ ### Side-loading
638
+
639
+ Any Phlex class that inherits `Proscenium::Phlex` will automatically be [side-loaded](#side-loading).
640
+
641
+ ### CSS Modules
642
+
643
+ [CSS Modules](#css-modules) are fully supported in Phlex classes, with access to the [`css_module` helper](#in-your-views) if you need it. However, there is a better and more seemless way to reference CSS module classes in your Phlex classes.
644
+
645
+ Within your Phlex classes, any class names that begin with `@` will be treated as a CSS module class.
646
+
647
+ ```ruby
648
+ # /app/views/users/show_view.rb
649
+ class Users::ShowView < Proscenium::Phlex
650
+ def template
651
+ h1 class: :@user_name do
652
+ @user.name
653
+ end
654
+ end
655
+ end
656
+ ```
657
+
658
+ ```css
659
+ /* /app/views/users/show_view.module.css */
660
+ .userName {
661
+ color: red;
662
+ font-size: 50px;
663
+ }
664
+ ```
665
+
666
+ In the above `Users::ShowView` Phlex class, the `@user_name` class will be resolved to the `userName` class in the `users/show_view.module.css` file.
667
+
668
+ The view above will be rendered something like this:
669
+
670
+ ```html
671
+ <h1 class="user_name-ABCD1234"></h1>
672
+ ```
673
+
674
+ You can of course continue to reference regular class names in your view, and they will be passed through as is. This will allow you to mix and match CSS modules and regular CSS classes in your views.
675
+
676
+ ```ruby
677
+ # /app/views/users/show_view.rb
678
+ class Users::ShowView < Proscenium::Phlex
679
+ def template
680
+ h1 class: :[@user_name, :title] do
681
+ @user.name
682
+ end
683
+ end
684
+ end
685
+ ```
686
+
687
+ ```html
688
+ <h1 class="user_name-ABCD1234 title">Joel Moss</h1>
689
+ ```
593
690
 
594
691
  ## ViewComponent Support
595
692
 
596
- > *docs needed*
693
+ [ViewComponent](https://viewcomponent.org/) iA framework for creating reusable, testable & encapsulated view components, built to integrate seamlessly with Ruby on Rails. Proscenium works perfectly with ViewComponent, with support for side-loading, CSS modules, and more. Simply write your ViewComponent classes and inherit from `Proscenium::ViewComponent`.
694
+
695
+ ```ruby
696
+ class MyView < Proscenium::ViewComponent
697
+ def call
698
+ tag.h1 'Hello World'
699
+ end
700
+ end
701
+ ```
702
+
703
+ ### Side-loading
704
+
705
+ Any ViewComponent class that inherits `Proscenium::ViewComponent` will automatically be [side-loaded](#side-loading).
706
+
707
+ ### CSS Modules
708
+
709
+ [CSS Modules](#css-modules) are fully supported in ViewComponent classes, with access to the [`css_module` helper](#in-your-views) if you need it.
710
+
711
+ ```ruby
712
+ # /app/components/user_component.rb
713
+ class UserComponent < Proscenium::ViewComponent
714
+ def template
715
+ div.h1 @user.name, class: css_module(:user_name)
716
+ end
717
+ end
718
+ ```
719
+
720
+ ```css
721
+ /* # /app/components/user_component.module.css */
722
+ .userName {
723
+ color: red;
724
+ font-size: 50px;
725
+ }
726
+ ```
727
+
728
+ The view above will be rendered something like this:
729
+
730
+ ```html
731
+ <h1 class="user_name-ABCD1234">Joel Moss</h1>
732
+ ```
597
733
 
598
734
  ## Cache Busting
599
735
 
@@ -617,7 +753,7 @@ The cache is set with a `max-age` of 30 days. You can customise this with the `c
617
753
  Rails.application.config.proscenium.cache_max_age = 12.months.to_i
618
754
  ```
619
755
 
620
- ## rjs is back!
756
+ ## rjs is back
621
757
 
622
758
  Proscenium brings back RJS! Any path ending in .rjs will be served from your Rails app. This allows you to import server rendered javascript.
623
759
 
@@ -654,7 +790,7 @@ bundle exec rake compile:local
654
790
  We have tests for both Ruby and Go. To run the Ruby tests:
655
791
 
656
792
  ```bash
657
- bundle exec rake test
793
+ bundle exec sus
658
794
  ```
659
795
 
660
796
  To run the Go tests:
@@ -671,7 +807,7 @@ go test ./internal/builder -bench=. -run="^$" -count=10 -benchmem
671
807
 
672
808
  ## Contributing
673
809
 
674
- 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).
810
+ 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).
675
811
 
676
812
  ## License
677
813
 
@@ -73,22 +73,26 @@ module Proscenium
73
73
  end
74
74
 
75
75
  def build(path)
76
- result = Request.build(path, @base_url, import_map, env_vars.to_json,
77
- @root.to_s,
78
- Rails.env.to_sym,
79
- Proscenium.config.code_splitting,
80
- Proscenium.config.debug)
76
+ ActiveSupport::Notifications.instrument('build.proscenium', identifier: path) do
77
+ result = Request.build(path, @base_url, import_map, env_vars.to_json,
78
+ @root.to_s,
79
+ Rails.env.to_sym,
80
+ Proscenium.config.code_splitting,
81
+ Proscenium.config.debug)
81
82
 
82
- raise BuildError.new(path, result[:response]) unless result[:success]
83
+ raise BuildError.new(path, result[:response]) unless result[:success]
83
84
 
84
- result[:response]
85
+ result[:response]
86
+ end
85
87
  end
86
88
 
87
89
  def resolve(path)
88
- result = Request.resolve(path, import_map, @root.to_s, Rails.env.to_sym)
89
- raise ResolveError.new(path, result[:response]) unless result[:success]
90
+ ActiveSupport::Notifications.instrument('resolve.proscenium', identifier: path) do
91
+ result = Request.resolve(path, import_map, @root.to_s, Rails.env.to_sym)
92
+ raise ResolveError.new(path, result[:response]) unless result[:success]
90
93
 
91
- result[:response]
94
+ result[:response]
95
+ end
92
96
  end
93
97
 
94
98
  private
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium
4
+ module CssModule::Path
5
+ # Returns the path to the CSS module file for this class, where the file is located alongside
6
+ # the class file, and has the same name as the class file, but with a `.module.css` extension.
7
+ #
8
+ # If the CSS module file does not exist, it's ancestry is checked, returning the first that
9
+ # exists. Then finally `nil` is returned if never found.
10
+ #
11
+ # @return [Pathname]
12
+ def css_module_path
13
+ return @css_module_path if instance_variable_defined?(:@css_module_path)
14
+
15
+ path = source_path.sub_ext('.module.css')
16
+ @css_module_path = path.exist? ? path : nil
17
+
18
+ unless @css_module_path
19
+ klass = superclass
20
+
21
+ while klass.respond_to?(:css_module_path) && !klass.abstract_class
22
+ break if (@css_module_path = klass.css_module_path)
23
+
24
+ klass = klass.superclass
25
+ end
26
+ end
27
+
28
+ @css_module_path
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium
4
+ class CssModule::Transformer
5
+ FILE_EXT = '.module.css'
6
+
7
+ def self.class_names(path, *names)
8
+ new(path).class_names(*names)
9
+ end
10
+
11
+ def initialize(source_path)
12
+ @source_path = source_path
13
+ @source_path = Pathname.new(@source_path) unless @source_path.is_a?(Pathname)
14
+ @source_path = @source_path.sub_ext(FILE_EXT) unless @source_path.to_s.end_with?(FILE_EXT)
15
+ end
16
+
17
+ # Transform each of the given class `names` to their respective CSS module name, which consist
18
+ # of the name, and suffixed with the digest of the resolved source path.
19
+ #
20
+ # Any name beginning with '@' will be transformed to a CSS module name. If `require_prefix` is
21
+ # false, then all names will be transformed to a CSS module name regardless of whether or not
22
+ # they begin with '@'.
23
+ #
24
+ # class_names :@my_module_name, :my_class_name
25
+ #
26
+ # Note that the generated digest is based on the resolved (URL) path, not the original path.
27
+ #
28
+ # You can also provide a path specifier and class name. The path will be the URL path to a
29
+ # stylesheet. The class name will be the name of the class to transform.
30
+ #
31
+ # class_names "/lib/button@default"
32
+ # class_names "mypackage/button@large"
33
+ # class_names "@scoped/package/button@small"
34
+ #
35
+ # @param names [String,Symbol,Array<String,Symbol>]
36
+ # @param require_prefix: [Boolean] whether or not to require the `@` prefix.
37
+ # @return [Array<String>] the transformed CSS module names.
38
+ def class_names(*names, require_prefix: true)
39
+ names.map do |name|
40
+ name = name.to_s if name.is_a?(Symbol)
41
+
42
+ if name.include?('/')
43
+ if name.start_with?('@')
44
+ # Scoped bare specifier (eg. "@scoped/package/lib/button@default").
45
+ _, path, name = name.split('@')
46
+ path = "@#{path}"
47
+ elsif name.start_with?('/')
48
+ # Local path with leading slash.
49
+ path, name = name[1..].split('@')
50
+ else
51
+ # Bare specifier (eg. "mypackage/lib/button@default").
52
+ path, name = name.split('@')
53
+ end
54
+
55
+ class_name! name, path: "#{path}#{FILE_EXT}"
56
+ elsif name.start_with?('@')
57
+ class_name! name[1..]
58
+ else
59
+ require_prefix ? name : class_name!(name)
60
+ end
61
+ end
62
+ end
63
+
64
+ def class_name!(name, path: @source_path)
65
+ resolved_path = Resolver.resolve(path.to_s)
66
+ digest = Importer.import(resolved_path)
67
+
68
+ sname = name.to_s
69
+ if sname.start_with?('_')
70
+ "_#{sname[1..]}-#{digest}"
71
+ else
72
+ "#{sname}-#{digest}"
73
+ end
74
+ end
75
+ end
76
+ end
@@ -3,41 +3,19 @@
3
3
  module Proscenium::CssModule
4
4
  extend ActiveSupport::Autoload
5
5
 
6
- class StylesheetNotFound < StandardError
7
- def initialize(pathname)
8
- @pathname = pathname
9
- super
10
- end
11
-
12
- def message
13
- "Stylesheet is required, but does not exist: #{@pathname}"
14
- end
15
- end
16
-
17
- autoload :ClassNamesResolver
18
- autoload :Resolver # deprecated
19
-
20
- # Like `css_modules`, but will raise if the stylesheet cannot be found.
21
- #
22
- # @param name [Array, String]
23
- def css_module!(names)
24
- cssm.class_names!(names).join ' '
25
- end
6
+ autoload :Path
7
+ autoload :Transformer
26
8
 
27
9
  # Accepts one or more CSS class names, and transforms them into CSS module names.
28
10
  #
29
- # @param name [Array, String]
30
- def css_module(names)
31
- cssm.class_names(names).join ' '
11
+ # @param name [String,Symbol,Array<String,Symbol>]
12
+ def css_module(*names)
13
+ cssm.class_names(*names, require_prefix: false).join ' '
32
14
  end
33
15
 
34
16
  private
35
17
 
36
- def path
37
- self.class.path
38
- end
39
-
40
18
  def cssm
41
- @cssm ||= Resolver.new(path)
19
+ @cssm ||= Transformer.new(self.class.css_module_path)
42
20
  end
43
21
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium
4
+ NotIncludedError = Class.new(StandardError)
5
+
6
+ module EnsureLoaded
7
+ def self.included(child)
8
+ child.class_eval do
9
+ append_after_action do
10
+ if request.format.html? && Importer.imported?
11
+ if Importer.js_imported?
12
+ raise NotIncludedError, 'There are javascripts to be included, but they have ' \
13
+ 'not been included in the page. Did you forget to add the ' \
14
+ '`#include_javascripts` helper in your views?'
15
+ end
16
+
17
+ if Importer.css_imported?
18
+ raise NotIncludedError, 'There are stylesheets to be included, but they have ' \
19
+ 'not been included in the page. Did you forget to add the ' \
20
+ '`#include_stylesheets` helper in your views?'
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
Binary file
@@ -15,5 +15,67 @@ module Proscenium
15
15
 
16
16
  super
17
17
  end
18
+
19
+ # Accepts one or more CSS class names, and transforms them into CSS module names.
20
+ #
21
+ # @see CssModule::Transformer#class_names
22
+ # @param name [String,Symbol,Array<String,Symbol>]
23
+ def css_module(*names)
24
+ path = Pathname.new(@lookup_context.find(@virtual_path).identifier).sub_ext('')
25
+ CssModule::Transformer.new(path).class_names(*names, require_prefix: false).join ' '
26
+ end
27
+
28
+ def include_stylesheets(**options)
29
+ out = []
30
+ Importer.each_stylesheet(delete: true) do |path, _path_options|
31
+ out << stylesheet_link_tag(path, extname: false, **options)
32
+ end
33
+ out.join("\n").html_safe
34
+ end
35
+ alias side_load_stylesheets include_stylesheets
36
+ deprecate side_load_stylesheets: 'Use `include_stylesheets` instead', deprecator: Deprecator.new
37
+
38
+ def include_javascripts(**options) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
39
+ out = []
40
+
41
+ if Rails.application.config.proscenium.code_splitting && Importer.multiple_js_imported?
42
+ imports = Importer.imported.dup
43
+
44
+ paths_to_build = []
45
+ Importer.each_javascript(delete: true) do |x, _|
46
+ paths_to_build << x.delete_prefix('/')
47
+ end
48
+
49
+ result = Builder.build(paths_to_build.join(';'), base_url: request.base_url)
50
+
51
+ # Remove the react components from the results, so they are not side loaded. Instead they
52
+ # are lazy loaded by the component manager.
53
+
54
+ scripts = {}
55
+ result.split(';').each do |x|
56
+ inpath, outpath = x.split('::')
57
+ inpath.prepend '/'
58
+ outpath.delete_prefix! 'public'
59
+
60
+ next unless imports.key?(inpath)
61
+
62
+ if (import = imports[inpath]).delete(:lazy)
63
+ scripts[inpath] = import.merge(outpath: outpath)
64
+ else
65
+ out << javascript_include_tag(outpath, extname: false, **options)
66
+ end
67
+ end
68
+
69
+ out << javascript_tag("window.prosceniumLazyScripts = #{scripts.to_json}")
70
+ else
71
+ Importer.each_javascript(delete: true) do |path, _path_options|
72
+ out << javascript_include_tag(path, extname: false, **options)
73
+ end
74
+ end
75
+
76
+ out.join("\n").html_safe
77
+ end
78
+ alias side_load_javascripts include_javascripts
79
+ deprecate side_load_javascripts: 'Use `include_javascripts` instead', deprecator: Deprecator.new
18
80
  end
19
81
  end