proscenium 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/CODE_OF_CONDUCT.md +84 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +908 -0
  5. data/lib/proscenium/builder.rb +189 -0
  6. data/lib/proscenium/core_ext/object/css_module_ivars.rb +19 -0
  7. data/lib/proscenium/css_module/path.rb +31 -0
  8. data/lib/proscenium/css_module/rewriter.rb +44 -0
  9. data/lib/proscenium/css_module/transformer.rb +84 -0
  10. data/lib/proscenium/css_module.rb +57 -0
  11. data/lib/proscenium/ensure_loaded.rb +27 -0
  12. data/lib/proscenium/ext/proscenium +0 -0
  13. data/lib/proscenium/ext/proscenium.h +131 -0
  14. data/lib/proscenium/helper.rb +70 -0
  15. data/lib/proscenium/importer.rb +134 -0
  16. data/lib/proscenium/libs/custom_element.js +54 -0
  17. data/lib/proscenium/libs/react-manager/index.jsx +121 -0
  18. data/lib/proscenium/libs/react-manager/react.js +2 -0
  19. data/lib/proscenium/libs/stimulus-loading.js +65 -0
  20. data/lib/proscenium/libs/test.js +1 -0
  21. data/lib/proscenium/libs/ujs/class.js +15 -0
  22. data/lib/proscenium/libs/ujs/data_confirm.js +23 -0
  23. data/lib/proscenium/libs/ujs/data_disable_with.js +68 -0
  24. data/lib/proscenium/libs/ujs/index.js +9 -0
  25. data/lib/proscenium/log_subscriber.rb +37 -0
  26. data/lib/proscenium/middleware/base.rb +103 -0
  27. data/lib/proscenium/middleware/engines.rb +45 -0
  28. data/lib/proscenium/middleware/esbuild.rb +30 -0
  29. data/lib/proscenium/middleware/runtime.rb +18 -0
  30. data/lib/proscenium/middleware/url.rb +16 -0
  31. data/lib/proscenium/middleware.rb +76 -0
  32. data/lib/proscenium/monkey.rb +95 -0
  33. data/lib/proscenium/phlex/asset_inclusions.rb +17 -0
  34. data/lib/proscenium/phlex/css_modules.rb +79 -0
  35. data/lib/proscenium/phlex/react_component.rb +32 -0
  36. data/lib/proscenium/phlex.rb +42 -0
  37. data/lib/proscenium/railtie.rb +106 -0
  38. data/lib/proscenium/react_componentable.rb +95 -0
  39. data/lib/proscenium/resolver.rb +39 -0
  40. data/lib/proscenium/side_load.rb +155 -0
  41. data/lib/proscenium/source_path.rb +15 -0
  42. data/lib/proscenium/templates/rescues/build_error.html.erb +30 -0
  43. data/lib/proscenium/ui/breadcrumbs/component.module.css +14 -0
  44. data/lib/proscenium/ui/breadcrumbs/component.rb +79 -0
  45. data/lib/proscenium/ui/breadcrumbs/computed_element.rb +69 -0
  46. data/lib/proscenium/ui/breadcrumbs/control.rb +95 -0
  47. data/lib/proscenium/ui/breadcrumbs/mixins.css +83 -0
  48. data/lib/proscenium/ui/breadcrumbs.rb +72 -0
  49. data/lib/proscenium/ui/component.rb +11 -0
  50. data/lib/proscenium/ui/test.js +1 -0
  51. data/lib/proscenium/ui.rb +14 -0
  52. data/lib/proscenium/utils.rb +13 -0
  53. data/lib/proscenium/version.rb +5 -0
  54. data/lib/proscenium/view_component/css_modules.rb +11 -0
  55. data/lib/proscenium/view_component/react_component.rb +22 -0
  56. data/lib/proscenium/view_component/sideload.rb +4 -0
  57. data/lib/proscenium/view_component.rb +38 -0
  58. data/lib/proscenium.rb +70 -0
  59. metadata +228 -0
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ffi'
4
+ require 'oj'
5
+
6
+ module Proscenium
7
+ class Builder
8
+ class CompileError < StandardError; end
9
+
10
+ class Result < FFI::Struct
11
+ layout :success, :bool,
12
+ :response, :string
13
+ end
14
+
15
+ module Request
16
+ extend FFI::Library
17
+ ffi_lib Pathname.new(__dir__).join('ext/proscenium').to_s
18
+
19
+ enum :environment, [:development, 1, :test, :production]
20
+
21
+ attach_function :build_to_string, [
22
+ :string, # Path or entry point.
23
+ :string, # Base URL of the Rails app. eg. https://example.com
24
+ :string, # Path to import map, relative to root
25
+ :string, # ENV variables as a JSON string
26
+
27
+ # Config
28
+ :string, # Rails application root
29
+ :string, # Proscenium gem root
30
+ :environment, # Rails environment as a Symbol
31
+ :bool, # Code splitting enabled?
32
+ :string, # Engine names and paths as a JSON string
33
+ :bool # Debugging enabled?
34
+ ], Result.by_value
35
+
36
+ attach_function :build_to_path, [
37
+ :string, # Path or entry point. Multiple can be given by separating with a semi-colon
38
+ :string, # Base URL of the Rails app. eg. https://example.com
39
+ :string, # Path to import map, relative to root
40
+ :string, # ENV variables as a JSON string
41
+
42
+ # Config
43
+ :string, # Rails application root
44
+ :string, # Proscenium gem root
45
+ :environment, # Rails environment as a Symbol
46
+ :bool, # Code splitting enabled?
47
+ :string, # Engine names and paths as a JSON string
48
+ :bool # Debugging enabled?
49
+ ], Result.by_value
50
+
51
+ attach_function :resolve, [
52
+ :string, # path or entry point
53
+ :string, # path to import map, relative to root
54
+
55
+ # Config
56
+ :string, # Rails application root
57
+ :string, # Proscenium gem root
58
+ :environment, # Rails environment as a Symbol
59
+ :bool # debugging enabled?
60
+ ], Result.by_value
61
+ end
62
+
63
+ class BuildError < StandardError
64
+ attr_reader :error
65
+
66
+ def initialize(error)
67
+ @error = Oj.load(error, mode: :strict).deep_transform_keys(&:underscore)
68
+
69
+ msg = @error['text']
70
+ if (location = @error['location'])
71
+ msg << " at #{location['file']}:#{location['line']}:#{location['column']}"
72
+ end
73
+
74
+ super(msg)
75
+ end
76
+ end
77
+
78
+ class ResolveError < StandardError
79
+ attr_reader :error_msg, :path
80
+
81
+ def initialize(path, error_msg)
82
+ super("Failed to resolve '#{path}' -- #{error_msg}")
83
+ end
84
+ end
85
+
86
+ def self.build_to_path(path, root: nil, base_url: nil)
87
+ new(root:, base_url:).build_to_path(path)
88
+ end
89
+
90
+ def self.build_to_string(path, root: nil, base_url: nil)
91
+ new(root:, base_url:).build_to_string(path)
92
+ end
93
+
94
+ def self.resolve(path, root: nil)
95
+ new(root:).resolve(path)
96
+ end
97
+
98
+ def initialize(root: nil, base_url: nil)
99
+ @root = root || Rails.root
100
+ @base_url = base_url
101
+ end
102
+
103
+ def build_to_path(path)
104
+ ActiveSupport::Notifications.instrument('build_to_path.proscenium',
105
+ identifier: path,
106
+ cached: Proscenium.cache.exist?(path)) do
107
+ Proscenium.cache.fetch path do
108
+ result = Request.build_to_path(path, @base_url, import_map, env_vars.to_json,
109
+ @root.to_s,
110
+ gem_root,
111
+ Rails.env.to_sym,
112
+ Proscenium.config.code_splitting,
113
+ engines.to_json,
114
+ Proscenium.config.debug)
115
+
116
+ raise BuildError, result[:response] unless result[:success]
117
+
118
+ result[:response]
119
+ end
120
+ end
121
+ end
122
+
123
+ def build_to_string(path)
124
+ ActiveSupport::Notifications.instrument('build_to_string.proscenium', identifier: path) do
125
+ result = Request.build_to_string(path, @base_url, import_map, env_vars.to_json,
126
+ @root.to_s,
127
+ gem_root,
128
+ Rails.env.to_sym,
129
+ Proscenium.config.code_splitting,
130
+ engines.to_json,
131
+ Proscenium.config.debug)
132
+
133
+ raise BuildError, result[:response] unless result[:success]
134
+
135
+ result[:response]
136
+ end
137
+ end
138
+
139
+ def resolve(path)
140
+ ActiveSupport::Notifications.instrument('resolve.proscenium', identifier: path) do
141
+ result = Request.resolve(path, import_map, @root.to_s,
142
+ gem_root,
143
+ Rails.env.to_sym,
144
+ Proscenium.config.debug)
145
+ raise ResolveError.new(path, result[:response]) unless result[:success]
146
+
147
+ result[:response]
148
+ end
149
+ end
150
+
151
+ private
152
+
153
+ # Build the ENV variables as determined by `Proscenium.config.env_vars` and
154
+ # `Proscenium::DEFAULT_ENV_VARS` to pass to esbuild.
155
+ def env_vars
156
+ ENV['NODE_ENV'] = ENV.fetch('RAILS_ENV', nil)
157
+ ENV.slice(*Proscenium.config.env_vars + Proscenium::DEFAULT_ENV_VARS)
158
+ end
159
+
160
+ def cache_query_string
161
+ q = Proscenium.config.cache_query_string
162
+ q ? "--cache-query-string #{q}" : nil
163
+ end
164
+
165
+ def engines
166
+ Proscenium.config.engines.to_h { |e| [e.engine_name, e.root.to_s] }.tap do |x|
167
+ x['proscenium/ui'] = Proscenium.ui_path.to_s
168
+ end
169
+ end
170
+
171
+ def import_map
172
+ return unless (path = Rails.root&.join('config'))
173
+
174
+ if (json = path.join('import_map.json')).exist?
175
+ return json.relative_path_from(@root).to_s
176
+ end
177
+
178
+ if (js = path.join('import_map.js')).exist?
179
+ return js.relative_path_from(@root).to_s
180
+ end
181
+
182
+ nil
183
+ end
184
+
185
+ def gem_root
186
+ Pathname.new(__dir__).join('..', '..').to_s
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Object
4
+ def instance_variable_get(name)
5
+ name.is_a?(::Proscenium::CssModule::Name) ? super(name.to_sym) : super
6
+ end
7
+
8
+ def instance_variable_set(name, obj)
9
+ name.is_a?(::Proscenium::CssModule::Name) ? super(name.to_sym, obj) : super
10
+ end
11
+
12
+ def instance_variable_defined?(name)
13
+ name.is_a?(::Proscenium::CssModule::Name) ? super(name.to_sym) : super
14
+ end
15
+
16
+ def remove_instance_variable(name)
17
+ name.is_a?(::Proscenium::CssModule::Name) ? super(name.to_sym) : super
18
+ end
19
+ end
@@ -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,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ruby-next/language'
4
+ require 'proscenium/core_ext/object/css_module_ivars'
5
+
6
+ module Proscenium
7
+ module CssModule
8
+ class Rewriter < RubyNext::Language::Rewriters::Text
9
+ NAME = 'proscenium-css-module'
10
+
11
+ def rewrite(source)
12
+ source = source.gsub(/%i\[((@[\w@ ]+)|([\w@ ]+ @[\w@ ]+))\]/) do |_|
13
+ arr = ::Regexp.last_match(1).split.map do |x|
14
+ x.start_with?('@') ? css_module_string(x[1..]) : ":#{x}"
15
+ end
16
+ "[#{arr.join(',')}]"
17
+ end
18
+
19
+ source.gsub(/:@([\w]+)/) do |_|
20
+ context.track!(self)
21
+ css_module_string(::Regexp.last_match(1))
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def css_module_string(name)
28
+ if (path = Pathname.new(context.path).sub_ext('.module.css')).exist?
29
+ tname = Transformer.new(path).class_name!(name, name.dup).first
30
+ "Proscenium::CssModule::Name.new(:@#{name}, '#{tname}')"
31
+ else
32
+ "Proscenium::CssModule::Name.new(:@#{name}, css_module(:#{name}))"
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ RubyNext::Language.send :include_patterns=, []
40
+ RubyNext::Language.include_patterns << "#{Rails.root.join('app', 'components')}/*.rb"
41
+ RubyNext::Language.include_patterns << "#{Rails.root.join('app', 'views')}/*.rb"
42
+ RubyNext::Language.rewriters = [Proscenium::CssModule::Rewriter]
43
+
44
+ require 'ruby-next/language/runtime'
@@ -0,0 +1,84 @@
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
+ return unless (@source_path = source_path)
13
+
14
+ @source_path = Pathname.new(@source_path) unless @source_path.is_a?(Pathname)
15
+ @source_path = @source_path.sub_ext(FILE_EXT) unless @source_path.to_s.end_with?(FILE_EXT)
16
+ end
17
+
18
+ # Transform each of the given class `names` to their respective CSS module name, which consist
19
+ # of the name, and suffixed with the digest of the resolved source path.
20
+ #
21
+ # Any name beginning with '@' will be transformed to a CSS module name. If `require_prefix` is
22
+ # false, then all names will be transformed to a CSS module name regardless of whether or not
23
+ # they begin with '@'.
24
+ #
25
+ # class_names :@my_module_name, :my_class_name
26
+ #
27
+ # Note that the generated digest is based on the resolved (URL) path, not the original path.
28
+ #
29
+ # You can also provide a path specifier and class name. The path will be the URL path to a
30
+ # stylesheet. The class name will be the name of the class to transform.
31
+ #
32
+ # class_names "/lib/button@default"
33
+ # class_names "mypackage/button@large"
34
+ # class_names "@scoped/package/button@small"
35
+ #
36
+ # @param names [String,Symbol,Array<String,Symbol>]
37
+ # @param require_prefix: [Boolean] whether or not to require the `@` prefix.
38
+ # @return [Array<String>] the transformed CSS module names.
39
+ def class_names(*names, require_prefix: true)
40
+ names.map do |name|
41
+ original_name = name.dup
42
+ name = name.to_s if name.is_a?(Symbol)
43
+
44
+ if name.include?('/')
45
+ if name.start_with?('@')
46
+ # Scoped bare specifier (eg. "@scoped/package/lib/button@default").
47
+ _, path, name = name.split('@')
48
+ path = "@#{path}"
49
+ else
50
+ # Local path (eg. /some/path/to/button@default") or bare specifier (eg.
51
+ # "mypackage/lib/button@default").
52
+ path, name = name.split('@')
53
+ end
54
+
55
+ class_name! name, original_name, path: "#{path}#{FILE_EXT}"
56
+ elsif name.start_with?('@')
57
+ class_name! name[1..], original_name
58
+ else
59
+ require_prefix ? name : class_name!(name, original_name)
60
+ end
61
+ end
62
+ end
63
+
64
+ def class_name!(name, original_name, path: @source_path)
65
+ unless path
66
+ raise Proscenium::CssModule::TransformError.new(original_name, 'CSS module path not given')
67
+ end
68
+
69
+ resolved_path = Resolver.resolve(path.to_s)
70
+ digest = Importer.import(resolved_path)
71
+
72
+ transformed_path = ''
73
+ transformed_path = "__#{resolved_path[1..].gsub(%r{[/\.]}, '-')}" if Rails.env.development?
74
+ transformed_name = name.to_s
75
+ transformed_name = if transformed_name.start_with?('_')
76
+ "_#{transformed_name[1..]}-#{digest}#{transformed_path}"
77
+ else
78
+ "#{transformed_name}-#{digest}#{transformed_path}"
79
+ end
80
+
81
+ [transformed_name, resolved_path]
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium::CssModule
4
+ extend ActiveSupport::Autoload
5
+
6
+ autoload :Path
7
+ autoload :Transformer
8
+
9
+ class TransformError < StandardError
10
+ def initialize(name, additional_msg = nil)
11
+ msg = "Failed to transform CSS module `#{name}`"
12
+ msg << ' - ' << additional_msg if additional_msg
13
+
14
+ super(msg)
15
+ end
16
+ end
17
+
18
+ class Name
19
+ def initialize(name, transform)
20
+ @name = name
21
+ @transform = transform
22
+ end
23
+
24
+ def to_s
25
+ @transform
26
+ end
27
+
28
+ def to_sym
29
+ @name
30
+ end
31
+ end
32
+
33
+ # Accepts one or more CSS class names, and transforms them into CSS module names.
34
+ #
35
+ # @param name [String,Symbol,Array<String,Symbol>]
36
+ # @param path [Pathname] the path to the CSS module file to use for the transformation.
37
+ # @return [String] the transformed CSS module names concatenated as a string.
38
+ def css_module(*names, path: nil)
39
+ transformer = path.nil? ? cssm : Transformer.new(path)
40
+ transformer.class_names(*names, require_prefix: false).map { |name, _| name }.join(' ')
41
+ end
42
+
43
+ # @param name [String,Symbol,Array<String,Symbol>]
44
+ # @param path [Pathname] the path to the CSS file to use for the transformation.
45
+ # @return [String] the transformed CSS module names concatenated as a string.
46
+ def class_names(*names, path: nil)
47
+ names = names.flatten.compact
48
+ transformer = path.nil? ? cssm : Transformer.new(path)
49
+ transformer.class_names(*names).map { |name, _| name }.join(' ') unless names.empty?
50
+ end
51
+
52
+ private
53
+
54
+ def cssm
55
+ @cssm ||= Transformer.new(self.class.css_module_path)
56
+ end
57
+ 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 side loaded javascripts to be included, but ' \
13
+ 'they have not been included in the page. Did you forget ' \
14
+ 'to add the `#include_assets` helper in your views?'
15
+ end
16
+
17
+ if Importer.css_imported?
18
+ raise NotIncludedError, 'There are side loaded stylesheets to be included, but ' \
19
+ 'they have not been included in the page. Did you forget ' \
20
+ 'to add the `#include_assets` helper in your views?'
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
Binary file
@@ -0,0 +1,131 @@
1
+ /* Code generated by cmd/cgo; DO NOT EDIT. */
2
+
3
+ /* package joelmoss/proscenium */
4
+
5
+
6
+ #line 1 "cgo-builtin-export-prolog"
7
+
8
+ #include <stddef.h>
9
+
10
+ #ifndef GO_CGO_EXPORT_PROLOGUE_H
11
+ #define GO_CGO_EXPORT_PROLOGUE_H
12
+
13
+ #ifndef GO_CGO_GOSTRING_TYPEDEF
14
+ typedef struct { const char *p; ptrdiff_t n; } _GoString_;
15
+ #endif
16
+
17
+ #endif
18
+
19
+ /* Start of preamble from import "C" comments. */
20
+
21
+
22
+ #line 3 "main.go"
23
+
24
+ struct Result {
25
+ int success;
26
+ char* response;
27
+ };
28
+
29
+ #line 1 "cgo-generated-wrapper"
30
+
31
+
32
+ /* End of preamble from import "C" comments. */
33
+
34
+
35
+ /* Start of boilerplate cgo prologue. */
36
+ #line 1 "cgo-gcc-export-header-prolog"
37
+
38
+ #ifndef GO_CGO_PROLOGUE_H
39
+ #define GO_CGO_PROLOGUE_H
40
+
41
+ typedef signed char GoInt8;
42
+ typedef unsigned char GoUint8;
43
+ typedef short GoInt16;
44
+ typedef unsigned short GoUint16;
45
+ typedef int GoInt32;
46
+ typedef unsigned int GoUint32;
47
+ typedef long long GoInt64;
48
+ typedef unsigned long long GoUint64;
49
+ typedef GoInt64 GoInt;
50
+ typedef GoUint64 GoUint;
51
+ typedef size_t GoUintptr;
52
+ typedef float GoFloat32;
53
+ typedef double GoFloat64;
54
+ #ifdef _MSC_VER
55
+ #include <complex.h>
56
+ typedef _Fcomplex GoComplex64;
57
+ typedef _Dcomplex GoComplex128;
58
+ #else
59
+ typedef float _Complex GoComplex64;
60
+ typedef double _Complex GoComplex128;
61
+ #endif
62
+
63
+ /*
64
+ static assertion to make sure the file is being used on architecture
65
+ at least with matching size of GoInt.
66
+ */
67
+ typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
68
+
69
+ #ifndef GO_CGO_GOSTRING_TYPEDEF
70
+ typedef _GoString_ GoString;
71
+ #endif
72
+ typedef void *GoMap;
73
+ typedef void *GoChan;
74
+ typedef struct { void *t; void *v; } GoInterface;
75
+ typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
76
+
77
+ #endif
78
+
79
+ /* End of boilerplate cgo prologue. */
80
+
81
+ #ifdef __cplusplus
82
+ extern "C" {
83
+ #endif
84
+
85
+
86
+ // Build the given `path` in the `root`.
87
+ //
88
+ // BuildOptions
89
+ // - path - The path to build relative to `root`.
90
+ // - baseUrl - base URL of the Rails app. eg. https://example.com
91
+ // - importMap - Path to the import map relative to `root`.
92
+ // - envVars - JSON string of environment variables.
93
+ // Config:
94
+ // - root - The working directory.
95
+ // - env - The environment (1 = development, 2 = test, 3 = production)
96
+ // - codeSpitting?
97
+ // - debug?
98
+ //
99
+ extern struct Result build_to_string(char* filepath, char* baseUrl, char* importMap, char* envVars, char* appRoot, char* gemPath, unsigned int env, GoUint8 codeSplitting, char* engines, GoUint8 debug);
100
+
101
+ // Build the given `path` in the `root`.
102
+ //
103
+ // BuildOptions
104
+ // - path - The path to build relative to `root`. Multiple paths can be given by separating them
105
+ // with a semi-colon.
106
+ // - baseUrl - base URL of the Rails app. eg. https://example.com
107
+ // - importMap - Path to the import map relative to `root`.
108
+ // - envVars - JSON string of environment variables.
109
+ // Config:
110
+ // - root - The working directory.
111
+ // - env - The environment (1 = development, 2 = test, 3 = production)
112
+ // - codeSpitting?
113
+ // - debug?
114
+ //
115
+ extern struct Result build_to_path(char* filepath, char* baseUrl, char* importMap, char* envVars, char* appRoot, char* gemPath, unsigned int env, GoUint8 codeSplitting, char* engines, GoUint8 debug);
116
+
117
+ // Resolve the given `path` relative to the `root`.
118
+ //
119
+ // ResolveOptions
120
+ // - path - The path to build relative to `root`.
121
+ // - importMap - Path to the import map relative to `root`.
122
+ // Config
123
+ // - root - The working directory.
124
+ // - env - The environment (1 = development, 2 = test, 3 = production)
125
+ // - debug?
126
+ //
127
+ extern struct Result resolve(char* path, char* importMap, char* appRoot, char* gemPath, unsigned int env, GoUint8 debug);
128
+
129
+ #ifdef __cplusplus
130
+ }
131
+ #endif
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium
4
+ module Helper
5
+ def sideload_assets(value)
6
+ if value.nil?
7
+ @current_template.instance_variable_defined?(:@sideload_assets_options) &&
8
+ @current_template.remove_instance_variable(:@sideload_assets_options)
9
+ else
10
+ @current_template.instance_variable_set :@sideload_assets_options, value
11
+ end
12
+ end
13
+
14
+ # Overriden to allow regular use of javascript_include_tag and stylesheet_link_tag, while still
15
+ # building with Proscenium. It's important to note that `include_assets` will not call this, as
16
+ # those asset paths all begin with a slash, which the Rails asset helpers do not pass through to
17
+ # here.
18
+ def compute_asset_path(path, options = {})
19
+ if %i[javascript stylesheet].include?(options[:type])
20
+ result = Proscenium::Builder.build_to_path(path, base_url: request.base_url)
21
+ return result.split('::').last.delete_prefix 'public'
22
+ end
23
+
24
+ super
25
+ end
26
+
27
+ # Accepts one or more CSS class names, and transforms them into CSS module names.
28
+ #
29
+ # @see CssModule::Transformer#class_names
30
+ # @param name [String,Symbol,Array<String,Symbol>]
31
+ # @param path [Pathname] the path to the CSS module file to use for the transformation.
32
+ # @return [String] the transformed CSS module names concatenated as a string.
33
+ def css_module(*names, path: nil)
34
+ path ||= Pathname.new(@lookup_context.find(@virtual_path).identifier).sub_ext('')
35
+ CssModule::Transformer.new(path).class_names(*names, require_prefix: false)
36
+ .map { |name, _| name }.join(' ')
37
+ end
38
+
39
+ # @param name [String,Symbol,Array<String,Symbol>]
40
+ # @param path [Pathname] the path to the CSS file to use for the transformation.
41
+ # @return [String] the transformed CSS module names concatenated as a string.
42
+ def class_names(*names, path: nil)
43
+ names = names.flatten.compact
44
+
45
+ return if names.empty?
46
+
47
+ path ||= Pathname.new(@lookup_context.find(@virtual_path).identifier).sub_ext('')
48
+ CssModule::Transformer.new(path).class_names(*names).map { |name, _| name }.join(' ')
49
+ end
50
+
51
+ def include_assets
52
+ include_stylesheets + include_javascripts
53
+ end
54
+
55
+ def include_stylesheets
56
+ '<!-- [PROSCENIUM_STYLESHEETS] -->'.html_safe
57
+ end
58
+ alias side_load_stylesheets include_stylesheets
59
+ deprecate side_load_stylesheets: 'Use `include_stylesheets` instead', deprecator: Deprecator.new
60
+
61
+ # Includes all javascripts that have been imported and side loaded.
62
+ #
63
+ # @return [String] the HTML tags for the javascripts.
64
+ def include_javascripts
65
+ '<!-- [PROSCENIUM_LAZY_SCRIPTS] --><!-- [PROSCENIUM_JAVASCRIPTS] -->'.html_safe
66
+ end
67
+ alias side_load_javascripts include_javascripts
68
+ deprecate side_load_javascripts: 'Use `include_javascripts` instead', deprecator: Deprecator.new
69
+ end
70
+ end