proscenium 0.1.0.alpha2-arm64-darwin → 0.1.0.alpha4-arm64-darwin
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +216 -32
- data/app/components/react_component.rb +10 -3
- data/bin/esbuild +0 -0
- data/bin/{parcel_css → lightningcss} +0 -0
- data/config/routes.rb +1 -1
- data/lib/proscenium/compilers/esbuild/compile_error.js +147 -0
- data/lib/proscenium/compilers/esbuild/css/postcss.js +67 -0
- data/lib/proscenium/compilers/esbuild/css_plugin.js +176 -0
- data/lib/proscenium/compilers/esbuild/env_plugin.js +6 -4
- data/lib/proscenium/compilers/esbuild/http_bundle_plugin.js +53 -0
- data/lib/proscenium/compilers/esbuild/import_map.js +59 -0
- data/lib/proscenium/compilers/esbuild/resolve_plugin.js +67 -65
- data/lib/proscenium/compilers/esbuild/setup_plugin.js +32 -22
- data/lib/proscenium/{cli → compilers}/esbuild/solidjs_plugin.js +5 -4
- data/lib/proscenium/compilers/esbuild.bench.js +7 -3
- data/lib/proscenium/compilers/esbuild.js +93 -19
- data/lib/proscenium/css_module.rb +48 -6
- data/lib/proscenium/helper.rb +5 -3
- data/lib/proscenium/middleware/base.rb +13 -3
- data/lib/proscenium/middleware/esbuild.rb +17 -1
- data/lib/proscenium/middleware/lightningcss.rb +64 -0
- data/lib/proscenium/middleware.rb +7 -19
- data/lib/proscenium/phlex.rb +36 -0
- data/lib/proscenium/railtie.rb +10 -20
- data/lib/proscenium/runtime/auto_reload.js +3 -3
- data/lib/proscenium/side_load.rb +14 -40
- data/lib/proscenium/utils.js +8 -0
- data/lib/proscenium/version.rb +1 -1
- data/lib/proscenium/view_component.rb +5 -1
- data/lib/proscenium.rb +1 -0
- metadata +27 -19
- data/lib/proscenium/cli/argument_error.js +0 -24
- data/lib/proscenium/cli/builders/index.js +0 -1
- data/lib/proscenium/cli/builders/javascript.js +0 -45
- data/lib/proscenium/cli/builders/react.js +0 -60
- data/lib/proscenium/cli/builders/solid.js +0 -46
- data/lib/proscenium/cli/esbuild/env_plugin.js +0 -21
- data/lib/proscenium/cli/esbuild/resolve_plugin.js +0 -136
- data/lib/proscenium/cli/js_builder.js +0 -194
- data/lib/proscenium/cli/solid.js +0 -15
- data/lib/proscenium/cli/utils.js +0 -93
- data/lib/proscenium/middleware/parcel_css.rb +0 -37
- data/lib/proscenium/runtime/component_manager/index.js +0 -27
- data/lib/proscenium/runtime/component_manager/render_component.js +0 -40
- 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', '
|
14
|
-
boolean: ['write'],
|
15
|
-
alias: {
|
43
|
+
string: ['root', 'import-map'],
|
44
|
+
boolean: ['write', 'debug'],
|
45
|
+
alias: {
|
46
|
+
'import-map': 'importMap'
|
47
|
+
}
|
16
48
|
})
|
17
|
-
|
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 {
|
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
|
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: '
|
55
|
-
|
111
|
+
logLevel: 'silent',
|
112
|
+
logLimit: 1,
|
56
113
|
outdir: 'public/assets',
|
57
114
|
outbase: './',
|
58
115
|
format: 'esm',
|
59
|
-
|
60
|
-
|
116
|
+
jsx: 'automatic',
|
117
|
+
jsxDev: !isTest && !isProd,
|
61
118
|
minify: isProd,
|
62
119
|
bundle: true,
|
63
|
-
plugins: [envPlugin(), resolvePlugin({ runtimeDir, debug
|
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
|
-
|
70
|
-
|
125
|
+
if (!debug) {
|
126
|
+
params.sourcemap = isTest ? false : isProd ? 'linked' : 'inline'
|
127
|
+
}
|
71
128
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
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 =
|
16
|
+
@path = path
|
17
|
+
@css_module_path = "#{path}.module.css"
|
18
|
+
end
|
6
19
|
|
7
|
-
|
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
|
-
|
35
|
+
doc.to_html.html_safe
|
10
36
|
end
|
11
37
|
|
12
|
-
#
|
38
|
+
# @returns [Array] of class names generated from the given CSS module `names`.
|
13
39
|
def class_names(*names)
|
14
|
-
|
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("/#{@
|
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
|
data/lib/proscenium/helper.rb
CHANGED
@@ -11,7 +11,9 @@ module Proscenium
|
|
11
11
|
def side_load_stylesheets
|
12
12
|
return unless Proscenium::Current.loaded
|
13
13
|
|
14
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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) ||
|
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
|
-
|
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
|
-
|
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
|
data/lib/proscenium/railtie.rb
CHANGED
@@ -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
|
-
|
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.
|
66
|
+
next unless config.proscenium.auto_reload
|
77
67
|
|
78
|
-
@listener = Listen.to(*config.proscenium.
|
79
|
-
only: config.proscenium.
|
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.
|
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
|
10
|
+
console.log('[Proscenium] Files changed; reloading...')
|
11
11
|
location.reload()
|
12
12
|
}, 200),
|
13
13
|
|
14
14
|
connected() {
|
15
|
-
console.log('Proscenium
|
15
|
+
console.log('[Proscenium] Auto-reload websocket connected')
|
16
16
|
},
|
17
17
|
|
18
18
|
disconnected() {
|
19
|
-
console.log('Proscenium
|
19
|
+
console.log('[Proscenium] Auto-reload websocket disconnected')
|
20
20
|
}
|
21
21
|
})
|
22
22
|
}
|
data/lib/proscenium/side_load.rb
CHANGED
@@ -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
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
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
|
data/lib/proscenium/version.rb
CHANGED
@@ -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
|
17
|
+
cssm.class_names!(name).join ' '
|
14
18
|
end
|
15
19
|
|
16
20
|
private
|