proscenium 0.5.1-arm64-darwin → 0.7.0-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 +128 -92
- data/bin/proscenium +0 -0
- data/bin/proscenium.h +109 -0
- data/config/routes.rb +0 -3
- data/lib/proscenium/css_module/class_names_resolver.rb +66 -0
- data/lib/proscenium/css_module/resolver.rb +76 -0
- data/lib/proscenium/css_module.rb +18 -39
- data/lib/proscenium/esbuild/golib.rb +97 -0
- data/lib/proscenium/esbuild.rb +32 -0
- data/lib/proscenium/helper.rb +0 -23
- data/lib/proscenium/log_subscriber.rb +26 -0
- data/lib/proscenium/middleware/base.rb +28 -36
- data/lib/proscenium/middleware/esbuild.rb +18 -44
- data/lib/proscenium/middleware/url.rb +1 -6
- data/lib/proscenium/middleware.rb +12 -16
- data/lib/proscenium/phlex/component_concerns.rb +27 -0
- data/lib/proscenium/phlex/page.rb +62 -0
- data/lib/proscenium/phlex/react_component.rb +52 -8
- data/lib/proscenium/phlex/resolve_css_modules.rb +67 -0
- data/lib/proscenium/phlex.rb +34 -33
- data/lib/proscenium/railtie.rb +41 -67
- data/lib/proscenium/side_load/ensure_loaded.rb +25 -0
- data/lib/proscenium/side_load/helper.rb +25 -0
- data/lib/proscenium/side_load/monkey.rb +48 -0
- data/lib/proscenium/side_load.rb +58 -52
- data/lib/proscenium/version.rb +1 -1
- data/lib/proscenium/view_component/react_component.rb +14 -0
- data/lib/proscenium/view_component.rb +28 -18
- data/lib/proscenium.rb +79 -2
- metadata +35 -72
- data/app/channels/proscenium/connection.rb +0 -13
- data/app/channels/proscenium/reload_channel.rb +0 -9
- data/bin/esbuild +0 -0
- data/bin/lightningcss +0 -0
- data/lib/proscenium/compiler.js +0 -84
- data/lib/proscenium/compilers/esbuild/argument_error.js +0 -24
- data/lib/proscenium/compilers/esbuild/compile_error.js +0 -148
- data/lib/proscenium/compilers/esbuild/css/postcss.js +0 -67
- data/lib/proscenium/compilers/esbuild/css_plugin.js +0 -172
- data/lib/proscenium/compilers/esbuild/env_plugin.js +0 -46
- data/lib/proscenium/compilers/esbuild/http_bundle_plugin.js +0 -53
- data/lib/proscenium/compilers/esbuild/import_map.js +0 -59
- data/lib/proscenium/compilers/esbuild/resolve_plugin.js +0 -205
- data/lib/proscenium/compilers/esbuild/setup_plugin.js +0 -45
- data/lib/proscenium/compilers/esbuild/solidjs_plugin.js +0 -24
- data/lib/proscenium/compilers/esbuild.bench.js +0 -14
- data/lib/proscenium/compilers/esbuild.js +0 -179
- data/lib/proscenium/link_to_helper.rb +0 -40
- data/lib/proscenium/middleware/lightningcss.rb +0 -64
- data/lib/proscenium/middleware/outside_root.rb +0 -26
- data/lib/proscenium/middleware/runtime.rb +0 -22
- data/lib/proscenium/middleware/static.rb +0 -14
- data/lib/proscenium/phlex/component.rb +0 -9
- data/lib/proscenium/precompile.rb +0 -31
- data/lib/proscenium/runtime/auto_reload.js +0 -40
- data/lib/proscenium/runtime/react_shim/index.js +0 -1
- data/lib/proscenium/runtime/react_shim/package.json +0 -5
- data/lib/proscenium/utils.js +0 -8
- data/lib/tasks/assets.rake +0 -19
@@ -1,179 +0,0 @@
|
|
1
|
-
import { writeAll } from 'std/streams/mod.ts'
|
2
|
-
import { parse as parseArgs } from 'std/flags/mod.ts'
|
3
|
-
import { expandGlob } from 'std/fs/mod.ts'
|
4
|
-
import { join, isGlob, resolve, dirname, fromFileUrl } from 'std/path/mod.ts'
|
5
|
-
import { build, stop } from 'esbuild'
|
6
|
-
|
7
|
-
import { readImportMap } from './esbuild/import_map.js'
|
8
|
-
import envPlugin from './esbuild/env_plugin.js'
|
9
|
-
import cssPlugin from './esbuild/css_plugin.js'
|
10
|
-
import resolvePlugin from './esbuild/resolve_plugin.js'
|
11
|
-
import ArgumentError from './esbuild/argument_error.js'
|
12
|
-
import throwCompileError from './esbuild/compile_error.js'
|
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 or globs to compile.
|
27
|
-
*
|
28
|
-
* OPTIONS:
|
29
|
-
* --root <PATH>
|
30
|
-
* Relative or absolute path to the root or current working directory when compilation will
|
31
|
-
* take place.
|
32
|
-
* --import-map <PATH>
|
33
|
-
* Path to an import map, relative to the <root>.
|
34
|
-
* --lightningcss-bin <PATH>
|
35
|
-
* Path to the lightningcss CLI binary.
|
36
|
-
* --write
|
37
|
-
* Write output to the filesystem according to esbuild logic.
|
38
|
-
* --cache-query-string <STRING>
|
39
|
-
* Query string to append to all imports as a cache buster. Example: `v1`.
|
40
|
-
* --debug
|
41
|
-
* Debug output,
|
42
|
-
*/
|
43
|
-
if (import.meta.main) {
|
44
|
-
!Deno.env.get('RAILS_ENV') && Deno.env.set('RAILS_ENV', 'development')
|
45
|
-
|
46
|
-
const { _: paths, ...options } = parseArgs(Deno.args, {
|
47
|
-
string: ['root', 'import-map', 'lightningcss-bin', 'cache-query-string'],
|
48
|
-
boolean: ['write', 'debug'],
|
49
|
-
alias: {
|
50
|
-
'import-map': 'importMap',
|
51
|
-
'cache-query-string': 'cacheQueryString',
|
52
|
-
'lightningcss-bin': 'lightningcssBin'
|
53
|
-
}
|
54
|
-
})
|
55
|
-
|
56
|
-
let result = await main(paths, options)
|
57
|
-
|
58
|
-
// `result` is an error object, so return to stderr as JSON, and an exit code of 1.
|
59
|
-
if (isPlainObject(result)) {
|
60
|
-
result = new TextEncoder().encode(`(${throwCompileError()})(${JSON.stringify(result)})`)
|
61
|
-
}
|
62
|
-
|
63
|
-
await writeAll(Deno.stdout, result)
|
64
|
-
}
|
65
|
-
|
66
|
-
async function main(paths = [], options = {}) {
|
67
|
-
const { write, debug } = { write: false, ...options }
|
68
|
-
|
69
|
-
if (!Array.isArray(paths) || paths.length < 1) throw new ArgumentError('pathsRequired')
|
70
|
-
if (!options.root) throw new ArgumentError('rootRequired')
|
71
|
-
if (!options.lightningcssBin) throw new ArgumentError('lightningcssBinRequired')
|
72
|
-
|
73
|
-
const root = resolve(options.root)
|
74
|
-
|
75
|
-
// Make sure that `root` is a valid directory.
|
76
|
-
try {
|
77
|
-
const stat = Deno.lstatSync(root)
|
78
|
-
if (!stat.isDirectory) throw new ArgumentError('rootUnknown', { root })
|
79
|
-
} catch {
|
80
|
-
throw new ArgumentError('rootUnknown', { root })
|
81
|
-
}
|
82
|
-
|
83
|
-
const env = Deno.env.get('RAILS_ENV')
|
84
|
-
const isProd = env === 'production'
|
85
|
-
const isTest = env === 'test'
|
86
|
-
const entryPoints = new Set()
|
87
|
-
|
88
|
-
for (let i = 0; i < paths.length; i++) {
|
89
|
-
const path = paths[i]
|
90
|
-
|
91
|
-
if (isGlob(path)) {
|
92
|
-
for await (const file of expandGlob(path, { root })) {
|
93
|
-
file.isFile && entryPoints.add(file.path)
|
94
|
-
}
|
95
|
-
} else if (path.startsWith('/') || /^url:https?:\/\//.test(path)) {
|
96
|
-
// Path is absolute, or is prefixed with 'url:', so it must be outsideRoot, or Url. Don't
|
97
|
-
// prefix the root.
|
98
|
-
// See Proscenium::Middleware::[OutsideRoot|Url].
|
99
|
-
entryPoints.add(path)
|
100
|
-
} else {
|
101
|
-
entryPoints.add(join(root, path))
|
102
|
-
}
|
103
|
-
}
|
104
|
-
|
105
|
-
let importMap
|
106
|
-
try {
|
107
|
-
importMap = readImportMap(options.importMap, root)
|
108
|
-
} catch (error) {
|
109
|
-
return {
|
110
|
-
detail: error.stack,
|
111
|
-
text: `Cannot read/parse import map: ${error.message}`,
|
112
|
-
location: {
|
113
|
-
file: error.file
|
114
|
-
}
|
115
|
-
}
|
116
|
-
}
|
117
|
-
|
118
|
-
const runtimeDir = resolve(dirname(fromFileUrl(import.meta.url)), '../runtime')
|
119
|
-
|
120
|
-
const params = {
|
121
|
-
entryPoints: Array.from(entryPoints),
|
122
|
-
absWorkingDir: root,
|
123
|
-
logLevel: 'silent',
|
124
|
-
logLimit: 1,
|
125
|
-
outdir: 'public/assets',
|
126
|
-
outbase: './',
|
127
|
-
format: 'esm',
|
128
|
-
jsx: 'automatic',
|
129
|
-
jsxDev: !isTest && !isProd,
|
130
|
-
minify: isProd,
|
131
|
-
bundle: true,
|
132
|
-
|
133
|
-
// The Esbuild default places browser before module, but we're building for modern browsers
|
134
|
-
// which support esm. So we prioritise that. Some libraries export a "browser" build that still
|
135
|
-
// uses CJS.
|
136
|
-
mainFields: ['module', 'browser', 'main'],
|
137
|
-
|
138
|
-
plugins: [
|
139
|
-
envPlugin(),
|
140
|
-
resolvePlugin({ runtimeDir, importMap, debug, cacheQueryString: options.cacheQueryString }),
|
141
|
-
cssPlugin({ lightningcssBin: options.lightningcssBin, debug })
|
142
|
-
],
|
143
|
-
metafile: write,
|
144
|
-
write
|
145
|
-
}
|
146
|
-
|
147
|
-
if (!debug) {
|
148
|
-
params.sourcemap = isProd || isTest ? false : 'inline'
|
149
|
-
}
|
150
|
-
|
151
|
-
let result
|
152
|
-
try {
|
153
|
-
result = await build(params)
|
154
|
-
} catch (error) {
|
155
|
-
if (debug) {
|
156
|
-
throw error
|
157
|
-
}
|
158
|
-
|
159
|
-
return { ...error.errors[0] }
|
160
|
-
} finally {
|
161
|
-
stop()
|
162
|
-
}
|
163
|
-
|
164
|
-
if (write) {
|
165
|
-
return new TextEncoder().encode(JSON.stringify(result))
|
166
|
-
} else {
|
167
|
-
const fileIndex = params.sourcemap === 'linked' ? 1 : 0
|
168
|
-
return result.outputFiles[fileIndex].contents
|
169
|
-
}
|
170
|
-
}
|
171
|
-
|
172
|
-
export function isPlainObject(value) {
|
173
|
-
if (Object.prototype.toString.call(value) !== '[object Object]') return false
|
174
|
-
|
175
|
-
const prototype = Object.getPrototypeOf(value)
|
176
|
-
return prototype === null || prototype === Object.prototype
|
177
|
-
}
|
178
|
-
|
179
|
-
export default main
|
@@ -1,40 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Proscenium
|
4
|
-
module LinkToHelper
|
5
|
-
# Overrides ActionView::Helpers::UrlHelper#link_to to allow passing a component instance as the
|
6
|
-
# URL, which will build the URL from the component path, eg. `/components/my_component`. The
|
7
|
-
# resulting link tag will also populate the `data` attribute with the component props.
|
8
|
-
#
|
9
|
-
# Example:
|
10
|
-
# link_to 'Go to', MyComponent
|
11
|
-
#
|
12
|
-
# TODO: ummm, todo it! ;)
|
13
|
-
end
|
14
|
-
|
15
|
-
# Component handling for the `link_to` helper.
|
16
|
-
class LinkToComponentArguments
|
17
|
-
def initialize(options, name_argument_index, context)
|
18
|
-
@options = options
|
19
|
-
@name_argument_index = name_argument_index
|
20
|
-
@component = @options[@name_argument_index]
|
21
|
-
|
22
|
-
# We have to render the component, and then extract the props from the component. Rendering
|
23
|
-
# first ensures that we have all the correct props.
|
24
|
-
context.render @component
|
25
|
-
end
|
26
|
-
|
27
|
-
def helper_options
|
28
|
-
@options[@name_argument_index] = "/components#{@component.virtual_path}"
|
29
|
-
@options[@name_argument_index += 1] ||= {}
|
30
|
-
@options[@name_argument_index][:rel] = 'nofollow'
|
31
|
-
@options[@name_argument_index][:data] ||= {}
|
32
|
-
@options[@name_argument_index][:data][:component] = {
|
33
|
-
path: @component.virtual_path,
|
34
|
-
props: @component.props
|
35
|
-
}
|
36
|
-
|
37
|
-
@options
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
@@ -1,64 +0,0 @@
|
|
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
|
@@ -1,26 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Proscenium
|
4
|
-
class Middleware
|
5
|
-
# Provides a way to render files outside of the Rails root during non-production. This is
|
6
|
-
# primarily to support linked NPM modules, for example when using `pnpm link ...`.
|
7
|
-
class OutsideRoot < Esbuild
|
8
|
-
private
|
9
|
-
|
10
|
-
# @override [Esbuild] reassigns root to '/'.
|
11
|
-
def renderable?
|
12
|
-
old_root = root
|
13
|
-
@root = Pathname.new('/')
|
14
|
-
|
15
|
-
super
|
16
|
-
ensure
|
17
|
-
@root = old_root
|
18
|
-
end
|
19
|
-
|
20
|
-
# @override [Esbuild] does not remove leading slash, ensuring it is an absolute path.
|
21
|
-
def path
|
22
|
-
@request.path
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
@@ -1,22 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Proscenium
|
4
|
-
class Middleware
|
5
|
-
class Runtime < Esbuild
|
6
|
-
private
|
7
|
-
|
8
|
-
def renderable?
|
9
|
-
old_root = root
|
10
|
-
old_path_info = @request.path_info
|
11
|
-
|
12
|
-
@root = Pathname.new(__dir__).join('../')
|
13
|
-
@request.path_info = @request.path_info.sub(%r{^/proscenium-runtime/}, 'runtime/')
|
14
|
-
|
15
|
-
super
|
16
|
-
ensure
|
17
|
-
@request.path_info = old_path_info
|
18
|
-
@root = old_root
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
@@ -1,14 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Proscenium
|
4
|
-
module Middleware
|
5
|
-
# Serves static files from disk that end with .js or .css.
|
6
|
-
class Static < Base
|
7
|
-
def attempt
|
8
|
-
benchmark :static do
|
9
|
-
Rack::File.new(root, { 'X-Proscenium-Middleware' => 'static' }).call(@request.env)
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
@@ -1,31 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'open3'
|
4
|
-
|
5
|
-
module Proscenium
|
6
|
-
class Precompile
|
7
|
-
def self.call
|
8
|
-
new.call
|
9
|
-
end
|
10
|
-
|
11
|
-
def call
|
12
|
-
Rails.application.config.proscenium.glob_types.find do |type, globs|
|
13
|
-
cmd = "#{cli type} --root #{Rails.root} '#{globs.join "' '"}' --write"
|
14
|
-
_, stderr, status = Open3.capture3(cmd)
|
15
|
-
|
16
|
-
raise stderr unless status.success?
|
17
|
-
raise "#{type} compiliation failed -- #{stderr}" unless stderr.empty?
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
private
|
22
|
-
|
23
|
-
def cli(type)
|
24
|
-
if ENV['PROSCENIUM_TEST']
|
25
|
-
"deno run -q --import-map import_map.json -A lib/proscenium/compilers/#{type}.js"
|
26
|
-
else
|
27
|
-
Gem.bin_path 'proscenium', type.to_s
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
@@ -1,40 +0,0 @@
|
|
1
|
-
import { createConsumer } from 'https://esm.sh/v96/@rails/actioncable@7.0.4/es2022/actioncable.js'
|
2
|
-
|
3
|
-
export default socketPath => {
|
4
|
-
const uid = (Date.now() + ((Math.random() * 100) | 0)).toString()
|
5
|
-
const consumer = createConsumer(`${socketPath}?uid=${uid}`)
|
6
|
-
|
7
|
-
consumer.subscriptions.create('Proscenium::ReloadChannel', {
|
8
|
-
received: debounce(() => {
|
9
|
-
console.log('[Proscenium] Files changed; reloading...')
|
10
|
-
location.reload()
|
11
|
-
}, 200, true),
|
12
|
-
|
13
|
-
connected() {
|
14
|
-
console.log('[Proscenium] Auto-reload websocket connected')
|
15
|
-
},
|
16
|
-
|
17
|
-
disconnected() {
|
18
|
-
console.log('[Proscenium] Auto-reload websocket disconnected')
|
19
|
-
}
|
20
|
-
})
|
21
|
-
}
|
22
|
-
|
23
|
-
function debounce(func, wait, immediate) {
|
24
|
-
let timeout
|
25
|
-
|
26
|
-
return function () {
|
27
|
-
const args = arguments
|
28
|
-
|
29
|
-
const later = () => {
|
30
|
-
timeout = null
|
31
|
-
!immediate && func.apply(this, args)
|
32
|
-
}
|
33
|
-
|
34
|
-
const callNow = immediate && !timeout
|
35
|
-
clearTimeout(timeout)
|
36
|
-
timeout = setTimeout(later, wait)
|
37
|
-
|
38
|
-
callNow && func.apply(this, args)
|
39
|
-
}
|
40
|
-
}
|
@@ -1 +0,0 @@
|
|
1
|
-
export { createElement as reactCreateElement, Fragment as ReactFragment } from 'react'
|
data/lib/proscenium/utils.js
DELETED
data/lib/tasks/assets.rake
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
namespace :proscenium do
|
4
|
-
desc 'Compile all your assets with Proscenium'
|
5
|
-
task precompile: :environment do
|
6
|
-
puts 'Precompiling assets with Proscenium...'
|
7
|
-
Proscenium::Precompile.call
|
8
|
-
puts 'Proscenium successfully precompiled your assets 🎉'
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
if Rake::Task.task_defined?('assets:precompile')
|
13
|
-
Rake::Task['assets:precompile'].enhance do
|
14
|
-
Rake::Task['proscenium:precompile'].invoke
|
15
|
-
end
|
16
|
-
else
|
17
|
-
Rake::Task.define_task('assets:precompile' => ['proscenium:precompile'])
|
18
|
-
Rake::Task.define_task('assets:clean') # null task just so Heroku builds don't fail
|
19
|
-
end
|