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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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