proscenium 0.1.0.alpha2-x86_64-darwin → 0.1.0.alpha4-x86_64-darwin

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +216 -32
  3. data/app/components/react_component.rb +10 -3
  4. data/bin/esbuild +0 -0
  5. data/bin/{parcel_css → lightningcss} +0 -0
  6. data/config/routes.rb +1 -1
  7. data/lib/proscenium/compilers/esbuild/compile_error.js +147 -0
  8. data/lib/proscenium/compilers/esbuild/css/postcss.js +67 -0
  9. data/lib/proscenium/compilers/esbuild/css_plugin.js +176 -0
  10. data/lib/proscenium/compilers/esbuild/env_plugin.js +6 -4
  11. data/lib/proscenium/compilers/esbuild/http_bundle_plugin.js +53 -0
  12. data/lib/proscenium/compilers/esbuild/import_map.js +59 -0
  13. data/lib/proscenium/compilers/esbuild/resolve_plugin.js +67 -65
  14. data/lib/proscenium/compilers/esbuild/setup_plugin.js +32 -22
  15. data/lib/proscenium/{cli → compilers}/esbuild/solidjs_plugin.js +5 -4
  16. data/lib/proscenium/compilers/esbuild.bench.js +7 -3
  17. data/lib/proscenium/compilers/esbuild.js +93 -19
  18. data/lib/proscenium/css_module.rb +48 -6
  19. data/lib/proscenium/helper.rb +5 -3
  20. data/lib/proscenium/middleware/base.rb +13 -3
  21. data/lib/proscenium/middleware/esbuild.rb +17 -1
  22. data/lib/proscenium/middleware/lightningcss.rb +64 -0
  23. data/lib/proscenium/middleware.rb +7 -19
  24. data/lib/proscenium/phlex.rb +36 -0
  25. data/lib/proscenium/railtie.rb +10 -20
  26. data/lib/proscenium/runtime/auto_reload.js +3 -3
  27. data/lib/proscenium/side_load.rb +14 -40
  28. data/lib/proscenium/utils.js +8 -0
  29. data/lib/proscenium/version.rb +1 -1
  30. data/lib/proscenium/view_component.rb +5 -1
  31. data/lib/proscenium.rb +1 -0
  32. metadata +27 -19
  33. data/lib/proscenium/cli/argument_error.js +0 -24
  34. data/lib/proscenium/cli/builders/index.js +0 -1
  35. data/lib/proscenium/cli/builders/javascript.js +0 -45
  36. data/lib/proscenium/cli/builders/react.js +0 -60
  37. data/lib/proscenium/cli/builders/solid.js +0 -46
  38. data/lib/proscenium/cli/esbuild/env_plugin.js +0 -21
  39. data/lib/proscenium/cli/esbuild/resolve_plugin.js +0 -136
  40. data/lib/proscenium/cli/js_builder.js +0 -194
  41. data/lib/proscenium/cli/solid.js +0 -15
  42. data/lib/proscenium/cli/utils.js +0 -93
  43. data/lib/proscenium/middleware/parcel_css.rb +0 -37
  44. data/lib/proscenium/runtime/component_manager/index.js +0 -27
  45. data/lib/proscenium/runtime/component_manager/render_component.js +0 -40
  46. data/lib/proscenium/runtime/import_css.js +0 -46
@@ -4,24 +4,66 @@ import { expandGlob } from 'std/fs/mod.ts'
4
4
  import { join, isGlob, resolve, dirname, fromFileUrl } from 'std/path/mod.ts'
5
5
  import { build, stop } from 'esbuild'
6
6
 
7
+ import { readImportMap } from './esbuild/import_map.js'
7
8
  import envPlugin from './esbuild/env_plugin.js'
9
+ import cssPlugin from './esbuild/css_plugin.js'
8
10
  import resolvePlugin from './esbuild/resolve_plugin.js'
9
11
  import ArgumentError from './esbuild/argument_error.js'
12
+ import throwCompileError from './esbuild/compile_error.js'
10
13
 
14
+ /**
15
+ * Compile the given paths, outputting the result to stdout. This is designed to be called as a CLI:
16
+ *
17
+ * Example with Deno run (dev and test):
18
+ * deno run -A lib/proscenium/compilers/esbuild.js --root ./test/internal lib/foo.js
19
+ * Example with Deno compiled binary:
20
+ * bin/esbuild lib/proscenium/compilers/esbuild.js --root ./test/internal lib/foo.js
21
+ *
22
+ * USAGE:
23
+ * esbuild [OPTIONS] <PATHS_ARG>...
24
+ *
25
+ * ARGS:
26
+ * <PATHS_ARG>... One or more file paths to compile.
27
+ *
28
+ * OPTIONS:
29
+ * --root
30
+ * Relative or absolute path to the root or current working directory when compilation will
31
+ * take place.
32
+ * --import-map
33
+ * Path to an import map, relative to the <root>.
34
+ * --write
35
+ * Write output to the filesystem according to esbuild logic.
36
+ * --debug
37
+ * Debug output,
38
+ */
11
39
  if (import.meta.main) {
40
+ !Deno.env.get('RAILS_ENV') && Deno.env.set('RAILS_ENV', 'development')
41
+
12
42
  const { _: paths, ...options } = parseArgs(Deno.args, {
13
- string: ['root', 'runtime-dir'],
14
- boolean: ['write'],
15
- alias: { 'runtime-dir': 'runtimeDir' }
43
+ string: ['root', 'import-map'],
44
+ boolean: ['write', 'debug'],
45
+ alias: {
46
+ 'import-map': 'importMap'
47
+ }
16
48
  })
17
- await writeAll(Deno.stdout, await main(paths, options))
49
+
50
+ let result = await main(paths, options)
51
+
52
+ // `result` is an error object, so return to stderr as JSON, and an exit code of 1.
53
+ if (isPlainObject(result)) {
54
+ result = new TextEncoder().encode(`(${throwCompileError()})(${JSON.stringify(result)})`)
55
+ }
56
+
57
+ await writeAll(Deno.stdout, result)
18
58
  }
19
59
 
20
60
  async function main(paths = [], options = {}) {
21
- const { root, write } = { write: false, ...options }
61
+ const { write, debug } = { write: false, ...options }
22
62
 
23
63
  if (!Array.isArray(paths) || paths.length < 1) throw new ArgumentError('pathsRequired')
24
- if (!root) throw new ArgumentError('rootRequired')
64
+ if (!options.root) throw new ArgumentError('rootRequired')
65
+
66
+ const root = resolve(options.root)
25
67
 
26
68
  // Make sure that `root` is a valid directory.
27
69
  try {
@@ -31,7 +73,9 @@ async function main(paths = [], options = {}) {
31
73
  throw new ArgumentError('rootUnknown', { root })
32
74
  }
33
75
 
34
- const isProd = Deno.env.get('RAILS_ENV') === 'production'
76
+ const env = Deno.env.get('RAILS_ENV')
77
+ const isProd = env === 'production'
78
+ const isTest = env === 'test'
35
79
 
36
80
  const entryPoints = new Set()
37
81
  for (let i = 0; i < paths.length; i++) {
@@ -46,37 +90,67 @@ async function main(paths = [], options = {}) {
46
90
  }
47
91
  }
48
92
 
93
+ let importMap
94
+ try {
95
+ importMap = readImportMap(options.importMap, root)
96
+ } catch (error) {
97
+ return {
98
+ detail: error.stack,
99
+ text: `Cannot read/parse import map: ${error.message}`,
100
+ location: {
101
+ file: error.file
102
+ }
103
+ }
104
+ }
105
+
49
106
  const runtimeDir = resolve(dirname(fromFileUrl(import.meta.url)), '../runtime')
50
107
 
51
108
  const params = {
52
109
  entryPoints: Array.from(entryPoints),
53
110
  absWorkingDir: root,
54
- logLevel: 'error',
55
- sourcemap: isProd ? 'linked' : 'inline',
111
+ logLevel: 'silent',
112
+ logLimit: 1,
56
113
  outdir: 'public/assets',
57
114
  outbase: './',
58
115
  format: 'esm',
59
- jsxFactory: 'reactCreateElement',
60
- jsxFragment: 'ReactFragment',
116
+ jsx: 'automatic',
117
+ jsxDev: !isTest && !isProd,
61
118
  minify: isProd,
62
119
  bundle: true,
63
- plugins: [envPlugin(), resolvePlugin({ runtimeDir, debug: false })],
64
- inject: [join(runtimeDir, 'react_shim/index.js')],
120
+ plugins: [envPlugin(), resolvePlugin({ runtimeDir, importMap, debug }), cssPlugin({ debug })],
65
121
  metafile: write,
66
122
  write
67
123
  }
68
124
 
69
- try {
70
- const result = await build(params)
125
+ if (!debug) {
126
+ params.sourcemap = isTest ? false : isProd ? 'linked' : 'inline'
127
+ }
71
128
 
72
- if (write) {
73
- return new TextEncoder().encode(JSON.stringify(result))
74
- } else {
75
- return result.outputFiles[0].contents
129
+ let result
130
+ try {
131
+ result = await build(params)
132
+ } catch (error) {
133
+ if (debug) {
134
+ throw error
76
135
  }
136
+
137
+ return { ...error.errors[0] }
77
138
  } finally {
78
139
  stop()
79
140
  }
141
+
142
+ if (write) {
143
+ return new TextEncoder().encode(JSON.stringify(result))
144
+ } else {
145
+ return result.outputFiles[0].contents
146
+ }
147
+ }
148
+
149
+ export function isPlainObject(value) {
150
+ if (Object.prototype.toString.call(value) !== '[object Object]') return false
151
+
152
+ const prototype = Object.getPrototypeOf(value)
153
+ return prototype === null || prototype === Object.prototype
80
154
  }
81
155
 
82
156
  export default main
@@ -1,22 +1,64 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Proscenium::CssModule
4
+ class NotFound < StandardError
5
+ def initialize(pathname)
6
+ @pathname = pathname
7
+ super
8
+ end
9
+
10
+ def message
11
+ "Stylesheet is required, but does not exist: #{@pathname}"
12
+ end
13
+ end
14
+
4
15
  def initialize(path)
5
- @path = "#{path}.module.css"
16
+ @path = path
17
+ @css_module_path = "#{path}.module.css"
18
+ end
6
19
 
7
- return unless Rails.application.config.proscenium.side_load
20
+ # Parses the given `content` for CSS modules names ('class' attributes beginning with '@'), and
21
+ # returns the content with said CSS Modules replaced with the compiled class names.
22
+ #
23
+ # Example:
24
+ # <div class="@my_css_module_name"></div>
25
+ def compile_class_names(content)
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
8
34
 
9
- Proscenium::SideLoad.append! Rails.root.join(@path)
35
+ doc.to_html.html_safe
10
36
  end
11
37
 
12
- # Returns an Array of class names generated from the given CSS module `names`.
38
+ # @returns [Array] of class names generated from the given CSS module `names`.
13
39
  def class_names(*names)
14
- names.flatten.compact.map { |name| "#{name}#{hash}" }
40
+ side_load_css_module
41
+ names.flatten.compact.map { |name| "#{name.to_s.camelize(:lower)}#{hash}" }
42
+ end
43
+
44
+ # Like #class_names, but requires that the stylesheet exists.
45
+ #
46
+ # @raises Proscenium::CssModule::NotFound if stylesheet does not exists.
47
+ def class_names!(...)
48
+ raise NotFound, @css_module_path unless Rails.root.join(@css_module_path).exist?
49
+
50
+ class_names(...)
15
51
  end
16
52
 
17
53
  private
18
54
 
19
55
  def hash
20
- @hash ||= Digest::SHA1.hexdigest("/#{@path}")[..7]
56
+ @hash ||= Digest::SHA1.hexdigest("/#{@css_module_path}")[..7]
57
+ end
58
+
59
+ def side_load_css_module
60
+ return unless Rails.application.config.proscenium.side_load
61
+
62
+ Proscenium::SideLoad.append "#{@path}.module", :css
21
63
  end
22
64
  end
@@ -11,7 +11,9 @@ module Proscenium
11
11
  def side_load_stylesheets
12
12
  return unless Proscenium::Current.loaded
13
13
 
14
- stylesheet_link_tag(*Proscenium::Current.loaded[:css])
14
+ Proscenium::Current.loaded[:css].map do |sheet|
15
+ stylesheet_link_tag(sheet, id: "_#{Digest::SHA1.hexdigest("/#{sheet}")[..7]}")
16
+ end.join("\n").html_safe
15
17
  end
16
18
 
17
19
  def side_load_javascripts(**options)
@@ -21,12 +23,12 @@ module Proscenium
21
23
  end
22
24
 
23
25
  def proscenium_dev
24
- return if !Rails.env.development? || !Proscenium::Railtie.websocket
26
+ return unless Proscenium.config.auto_reload
25
27
 
26
28
  javascript_tag %(
27
29
  import autoReload from '/proscenium-runtime/auto_reload.js';
28
30
  autoReload('#{Proscenium::Railtie.websocket_mount_path}');
29
- ), type: 'module'
31
+ ), type: 'module', defer: true
30
32
  end
31
33
  end
32
34
  end
@@ -7,7 +7,9 @@ module Proscenium
7
7
  class Base
8
8
  include ActiveSupport::Benchmarkable
9
9
 
10
- class Error < StandardError; end
10
+ # Error when the result of the build returns an error. For example, when esbuild returns
11
+ # errors.
12
+ class CompileError < StandardError; end
11
13
 
12
14
  def self.attempt(request)
13
15
  new(request).renderable!&.attempt
@@ -57,15 +59,23 @@ module Proscenium
57
59
  response.write content
58
60
  response.content_type = content_type
59
61
  response['X-Proscenium-Middleware'] = name
62
+
63
+ yield response if block_given?
64
+
60
65
  response.finish
61
66
  end
62
67
 
63
68
  def build(cmd)
64
69
  stdout, stderr, status = Open3.capture3(cmd)
65
70
 
66
- raise Error, stderr unless status.success?
71
+ unless status.success?
72
+ raise self.class::CompileError, stderr if status.exitstatus == 2
73
+
74
+ raise BuildError, stderr
75
+ end
76
+
67
77
  unless stderr.empty?
68
- raise "Proscenium build of #{name}:'#{@request.fullpath}' failed -- #{stderr}"
78
+ raise BuildError, "Proscenium build of #{name}:'#{@request.fullpath}' failed -- #{stderr}"
69
79
  end
70
80
 
71
81
  stdout
@@ -3,10 +3,24 @@
3
3
  module Proscenium
4
4
  class Middleware
5
5
  class Esbuild < Base
6
+ class CompileError < StandardError
7
+ attr_reader :detail
8
+
9
+ def initialize(detail)
10
+ @detail = ActiveSupport::HashWithIndifferentAccess.new(Oj.load(detail, mode: :strict))
11
+
12
+ super "#{@detail[:text]} in #{@detail[:location][:file]}:#{@detail[:location][:line]}"
13
+ end
14
+ end
15
+
6
16
  def attempt
7
17
  benchmark :esbuild do
8
18
  render_response build("#{cli} --root #{root} #{path}")
9
19
  end
20
+ rescue CompileError => e
21
+ render_response "export default #{e.detail.to_json}" do |response|
22
+ response['X-Proscenium-Middleware'] = 'Esbuild::CompileError'
23
+ end
10
24
  end
11
25
 
12
26
  private
@@ -17,7 +31,9 @@ module Proscenium
17
31
 
18
32
  def cli
19
33
  if ENV['PROSCENIUM_TEST']
20
- 'deno run -q --import-map import_map.json -A lib/proscenium/compilers/esbuild.js'
34
+ [
35
+ 'deno run -q --import-map import_map.json -A', 'lib/proscenium/compilers/esbuild.js'
36
+ ].join(' ')
21
37
  else
22
38
  Gem.bin_path 'proscenium', 'esbuild'
23
39
  end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'oj'
4
+
5
+ module Proscenium
6
+ class Middleware
7
+ class Lightningcss < Base
8
+ def attempt
9
+ benchmark :lightningcss do
10
+ with_custom_media { |path| build path }
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def with_custom_media
17
+ if custom_media?
18
+ Tempfile.create do |f|
19
+ contents = Pathname.new("#{root}#{@request.path}").read
20
+ f.write contents, "\n", custom_media_path.read
21
+ f.rewind
22
+
23
+ yield f.path
24
+ end
25
+ else
26
+ yield "#{root}#{@request.path}"
27
+ end
28
+ end
29
+
30
+ def build(path)
31
+ results = super("#{cli} #{cli_options.join ' '} #{path}")
32
+ render_response css_module? ? Oj.load(results, mode: :strict)['code'] : results
33
+ end
34
+
35
+ def custom_media?
36
+ @custom_media ||= custom_media_path.exist?
37
+ end
38
+
39
+ def custom_media_path
40
+ @custom_media_path ||= Rails.root.join('lib', 'custom_media_queries.css')
41
+ end
42
+
43
+ def cli
44
+ Gem.bin_path 'proscenium', 'lightningcss'
45
+ end
46
+
47
+ def cli_options
48
+ options = ['--nesting', '--targets', "'>= 0.25%'"]
49
+ options << '--custom-media' if custom_media?
50
+
51
+ if css_module?
52
+ hash = Digest::SHA1.hexdigest(@request.path)[..7]
53
+ options += ['--css-modules', '--css-modules-pattern', "'[local]#{hash}'"]
54
+ end
55
+
56
+ Rails.env.production? ? options << '-m' : options
57
+ end
58
+
59
+ def css_module?
60
+ @css_module ||= /\.module\.css$/i.match?(@request.path_info)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -4,17 +4,13 @@ module Proscenium
4
4
  class Middleware
5
5
  extend ActiveSupport::Autoload
6
6
 
7
+ # Error when the build command fails.
8
+ class BuildError < StandardError; end
9
+
7
10
  autoload :Base
8
11
  autoload :Esbuild
9
- autoload :ParcelCss
10
12
  autoload :Runtime
11
13
 
12
- MIDDLEWARE_CLASSES = {
13
- esbuild: Esbuild,
14
- parcelcss: ParcelCss,
15
- runtime: Runtime
16
- }.freeze
17
-
18
14
  def initialize(app)
19
15
  @app = app
20
16
  end
@@ -35,23 +31,15 @@ module Proscenium
35
31
  def attempt(request)
36
32
  return unless (type = find_type(request))
37
33
 
38
- file_handler.attempt(request.env) || MIDDLEWARE_CLASSES[type].attempt(request)
34
+ file_handler.attempt(request.env) || type.attempt(request)
39
35
  end
40
36
 
41
37
  # Returns the type of file being requested using Rails.application.config.proscenium.glob_types.
42
38
  def find_type(request)
43
- return :runtime if request.path_info.start_with?('/proscenium-runtime/')
44
-
45
- path = Rails.root.join(request.path[1..])
46
-
47
- type, = glob_types.find do |_, globs|
48
- # TODO: Look for the precompiled file in public/assets first
49
- # globs.any? { |glob| Rails.public_path.join('assets').glob(glob).any?(path) }
50
-
51
- globs.any? { |glob| Rails.root.glob(glob).any?(path) }
52
- end
39
+ path = Pathname.new(request.path)
53
40
 
54
- type
41
+ return Runtime if path.fnmatch?(glob_types[:runtime], File::FNM_EXTGLOB)
42
+ return Esbuild if path.fnmatch?(glob_types[:esbuild], File::FNM_EXTGLOB)
55
43
  end
56
44
 
57
45
  def file_handler
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium
4
+ class Phlex < ::Phlex::View
5
+ module Sideload
6
+ def template(...)
7
+ Proscenium::SideLoad.append self.class.path if Rails.application.config.proscenium.side_load
8
+
9
+ super
10
+ end
11
+ end
12
+
13
+ class << self
14
+ attr_accessor :path
15
+
16
+ def inherited(child)
17
+ path = caller_locations(1, 1)[0].path
18
+ child.path = path.delete_prefix(::Rails.root.to_s).delete_suffix('.rb')[1..]
19
+
20
+ child.prepend Sideload
21
+
22
+ super
23
+ end
24
+ end
25
+
26
+ def css_module(name)
27
+ cssm.class_names!(name).join ' '
28
+ end
29
+
30
+ private
31
+
32
+ def cssm
33
+ @cssm ||= Proscenium::CssModule.new(self.class.path)
34
+ end
35
+ end
36
+ end
@@ -12,16 +12,8 @@ module Proscenium
12
12
  #
13
13
  # See https://doc.deno.land/https://deno.land/std@0.145.0/path/mod.ts/~/globToRegExp
14
14
  DEFAULT_GLOB_TYPES = {
15
- esbuild: [
16
- 'lib/**/*.{js,jsx}',
17
- 'app/components/**/*.{js,jsx}',
18
- 'app/views/**/*.{js,jsx}'
19
- ],
20
- parcelcss: [
21
- 'lib/**/*.css',
22
- 'app/components/**/*.css',
23
- 'app/views/**/*.css'
24
- ]
15
+ esbuild: '/{config,app,lib,node_modules}/**.{js,mjs,jsx,css}',
16
+ runtime: '/proscenium-runtime/**.{js,jsx}'
25
17
  }.freeze
26
18
 
27
19
  class << self
@@ -34,18 +26,16 @@ module Proscenium
34
26
  isolate_namespace Proscenium
35
27
 
36
28
  config.proscenium = ActiveSupport::OrderedOptions.new
37
- config.proscenium.listen_paths ||= %w[lib app]
38
- config.proscenium.listen_extensions ||= /\.(css|jsx?)$/
39
29
  config.proscenium.side_load = true
30
+ config.proscenium.auto_reload = Rails.env.development?
31
+ config.proscenium.auto_reload_paths ||= %w[lib app config]
32
+ config.proscenium.auto_reload_extensions ||= /\.(css|jsx?)$/
40
33
 
41
34
  initializer 'proscenium.configuration' do |app|
42
35
  options = app.config.proscenium
43
36
 
44
37
  options.glob_types = DEFAULT_GLOB_TYPES if options.glob_types.blank?
45
-
46
- options.auto_refresh = true if options.auto_refresh.nil?
47
- options.listen = Rails.env.development? if options.listen.nil?
48
- options.listen_paths.filter! { |path| Dir.exist? path }
38
+ options.auto_reload_paths.filter! { |path| Dir.exist? path }
49
39
  options.cable_mount_path ||= '/proscenium-cable'
50
40
  options.cable_logger ||= Rails.logger
51
41
  end
@@ -73,10 +63,10 @@ module Proscenium
73
63
  end
74
64
 
75
65
  config.after_initialize do
76
- next unless config.proscenium.listen
66
+ next unless config.proscenium.auto_reload
77
67
 
78
- @listener = Listen.to(*config.proscenium.listen_paths,
79
- only: config.proscenium.listen_extensions) do |mod, add, rem|
68
+ @listener = Listen.to(*config.proscenium.auto_reload_paths,
69
+ only: config.proscenium.auto_reload_extensions) do |mod, add, rem|
80
70
  Proscenium::Railtie.websocket&.broadcast('reload', {
81
71
  modified: mod,
82
72
  removed: rem,
@@ -94,7 +84,7 @@ module Proscenium
94
84
  class << self
95
85
  def websocket
96
86
  return @websocket unless @websocket.nil?
97
- return unless config.proscenium.auto_refresh
87
+ return unless config.proscenium.auto_reload
98
88
 
99
89
  cable = ActionCable::Server::Configuration.new
100
90
  cable.cable = { adapter: 'async' }.with_indifferent_access
@@ -7,16 +7,16 @@ export default socketPath => {
7
7
 
8
8
  consumer.subscriptions.create('Proscenium::ReloadChannel', {
9
9
  received: debounce(() => {
10
- console.log('Proscenium files changed; reloading...')
10
+ console.log('[Proscenium] Files changed; reloading...')
11
11
  location.reload()
12
12
  }, 200),
13
13
 
14
14
  connected() {
15
- console.log('Proscenium auto reload websocket connected')
15
+ console.log('[Proscenium] Auto-reload websocket connected')
16
16
  },
17
17
 
18
18
  disconnected() {
19
- console.log('Proscenium auto reload websocket disconnected')
19
+ console.log('[Proscenium] Auto-reload websocket disconnected')
20
20
  }
21
21
  })
22
22
  }
@@ -5,17 +5,6 @@ module Proscenium
5
5
  DEFAULT_EXTENSIONS = %i[js css].freeze
6
6
  EXTENSIONS = %i[js css].freeze
7
7
 
8
- class NotFound < StandardError
9
- def initialize(pathname)
10
- @pathname = pathname
11
- super
12
- end
13
-
14
- def message
15
- "#{@pathname} does not exist"
16
- end
17
- end
18
-
19
8
  module_function
20
9
 
21
10
  # Side load the given asset `path`, by appending it to `Proscenium::Current.loaded`, which is a
@@ -46,40 +35,25 @@ module Proscenium
46
35
  end
47
36
  end
48
37
 
49
- # Like #append, but only accepts a single `path` argument, which must be a Pathname. Raises
50
- # `NotFound` if path does not exist,
51
- def append!(pathname)
52
- Proscenium::Current.loaded ||= EXTENSIONS.to_h { |e| [e, Set[]] }
53
-
54
- unless pathname.is_a?(Pathname)
55
- raise ArgumentError, "Argument `pathname` (#{pathname}) must be a Pathname"
56
- end
57
-
58
- ext = pathname.extname.sub('.', '').to_sym
59
- path = pathname.relative_path_from(Rails.root).to_s
60
-
61
- raise ArgumentError, "unsupported extension: #{ext}" unless EXTENSIONS.include?(ext)
62
-
63
- return if Proscenium::Current.loaded[ext].include?(path)
64
-
65
- raise NotFound, path unless pathname.exist?
66
-
67
- Proscenium::Current.loaded[ext] << path
68
-
69
- Rails.logger.debug "[Proscenium] Side loaded /#{path}"
70
- end
71
-
72
38
  module Monkey
73
39
  module TemplateRenderer
74
40
  private
75
41
 
76
42
  def render_template(view, template, layout_name, locals)
77
- if template.respond_to?(:virtual_path) &&
78
- template.respond_to?(:type) && template.type == :html
79
- if (layout = layout_name && find_layout(layout_name, locals.keys, [formats.first]))
80
- Proscenium::SideLoad.append "app/views/#{layout.virtual_path}" # layout
81
- end
82
-
43
+ layout = find_layout(layout_name, locals.keys, [formats.first])
44
+ renderable = template.instance_variable_get(:@renderable)
45
+
46
+ if template.is_a?(ActionView::Template::Renderable) &&
47
+ renderable.class < ::ViewComponent::Base && renderable.class.format == :html
48
+ # Side load controller rendered ViewComponent
49
+ Proscenium::SideLoad.append "app/views/#{layout.virtual_path}" if layout
50
+ Proscenium::SideLoad.append "app/views/#{renderable.virtual_path}"
51
+ elsif template.respond_to?(:virtual_path) &&
52
+ template.respond_to?(:type) && template.type == :html
53
+ # Side load regular view template.
54
+ Proscenium::SideLoad.append "app/views/#{layout.virtual_path}" if layout
55
+
56
+ # Try side loading the variant template
83
57
  if template.respond_to?(:variant) && template.variant
84
58
  Proscenium::SideLoad.append "app/views/#{template.virtual_path}+#{template.variant}"
85
59
  end
@@ -0,0 +1,8 @@
1
+ export async function fileExists(path) {
2
+ try {
3
+ const fileInfo = await Deno.stat(path)
4
+ return fileInfo.isFile
5
+ } catch {
6
+ return false
7
+ }
8
+ }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Proscenium
4
- VERSION = '0.1.0.alpha2'
4
+ VERSION = '0.1.0.alpha4'
5
5
  end
@@ -5,12 +5,16 @@ module Proscenium::ViewComponent
5
5
 
6
6
  autoload :TagBuilder
7
7
 
8
+ def render_in(...)
9
+ cssm.compile_class_names(super)
10
+ end
11
+
8
12
  def before_render
9
13
  side_load_assets unless self.class < ReactComponent
10
14
  end
11
15
 
12
16
  def css_module(name)
13
- cssm.class_names(name.to_s.camelize(:lower)).join ' '
17
+ cssm.class_names!(name).join ' '
14
18
  end
15
19
 
16
20
  private