proscenium 0.5.1-x86_64-linux → 0.7.0-x86_64-linux

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +128 -92
  3. data/lib/proscenium/css_module/class_names_resolver.rb +66 -0
  4. data/lib/proscenium/css_module/resolver.rb +76 -0
  5. data/lib/proscenium/css_module.rb +18 -39
  6. data/lib/proscenium/esbuild/golib.rb +97 -0
  7. data/lib/proscenium/esbuild.rb +32 -0
  8. data/lib/proscenium/ext/proscenium +0 -0
  9. data/lib/proscenium/ext/proscenium.h +109 -0
  10. data/lib/proscenium/helper.rb +0 -23
  11. data/lib/proscenium/log_subscriber.rb +26 -0
  12. data/lib/proscenium/middleware/base.rb +28 -36
  13. data/lib/proscenium/middleware/esbuild.rb +18 -44
  14. data/lib/proscenium/middleware/url.rb +1 -6
  15. data/lib/proscenium/middleware.rb +12 -16
  16. data/lib/proscenium/phlex/component_concerns.rb +27 -0
  17. data/lib/proscenium/phlex/page.rb +62 -0
  18. data/lib/proscenium/phlex/react_component.rb +52 -8
  19. data/lib/proscenium/phlex/resolve_css_modules.rb +67 -0
  20. data/lib/proscenium/phlex.rb +34 -33
  21. data/lib/proscenium/railtie.rb +41 -67
  22. data/lib/proscenium/side_load/ensure_loaded.rb +25 -0
  23. data/lib/proscenium/side_load/helper.rb +25 -0
  24. data/lib/proscenium/side_load/monkey.rb +48 -0
  25. data/lib/proscenium/side_load.rb +58 -52
  26. data/lib/proscenium/version.rb +1 -1
  27. data/lib/proscenium/view_component/react_component.rb +14 -0
  28. data/lib/proscenium/view_component.rb +28 -18
  29. data/lib/proscenium.rb +79 -2
  30. metadata +35 -74
  31. data/app/channels/proscenium/connection.rb +0 -13
  32. data/app/channels/proscenium/reload_channel.rb +0 -9
  33. data/bin/esbuild +0 -0
  34. data/bin/lightningcss +0 -0
  35. data/config/routes.rb +0 -7
  36. data/lib/proscenium/compiler.js +0 -84
  37. data/lib/proscenium/compilers/esbuild/argument_error.js +0 -24
  38. data/lib/proscenium/compilers/esbuild/compile_error.js +0 -148
  39. data/lib/proscenium/compilers/esbuild/css/postcss.js +0 -67
  40. data/lib/proscenium/compilers/esbuild/css_plugin.js +0 -172
  41. data/lib/proscenium/compilers/esbuild/env_plugin.js +0 -46
  42. data/lib/proscenium/compilers/esbuild/http_bundle_plugin.js +0 -53
  43. data/lib/proscenium/compilers/esbuild/import_map.js +0 -59
  44. data/lib/proscenium/compilers/esbuild/resolve_plugin.js +0 -205
  45. data/lib/proscenium/compilers/esbuild/setup_plugin.js +0 -45
  46. data/lib/proscenium/compilers/esbuild/solidjs_plugin.js +0 -24
  47. data/lib/proscenium/compilers/esbuild.bench.js +0 -14
  48. data/lib/proscenium/compilers/esbuild.js +0 -179
  49. data/lib/proscenium/link_to_helper.rb +0 -40
  50. data/lib/proscenium/middleware/lightningcss.rb +0 -64
  51. data/lib/proscenium/middleware/outside_root.rb +0 -26
  52. data/lib/proscenium/middleware/runtime.rb +0 -22
  53. data/lib/proscenium/middleware/static.rb +0 -14
  54. data/lib/proscenium/phlex/component.rb +0 -9
  55. data/lib/proscenium/precompile.rb +0 -31
  56. data/lib/proscenium/runtime/auto_reload.js +0 -40
  57. data/lib/proscenium/runtime/react_shim/index.js +0 -1
  58. data/lib/proscenium/runtime/react_shim/package.json +0 -5
  59. data/lib/proscenium/utils.js +0 -8
  60. 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,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Proscenium::Phlex::Component < Proscenium::Phlex
4
- private
5
-
6
- def virtual_path
7
- "/#{self.class.name.underscore}"
8
- end
9
- 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'
@@ -1,5 +0,0 @@
1
- {
2
- "name": "react_shim",
3
- "private": true,
4
- "sideEffects": false
5
- }
@@ -1,8 +0,0 @@
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,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