proscenium 0.1.0.alpha2-x86_64-linux → 0.1.0.alpha4-x86_64-linux
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.
- checksums.yaml +4 -4
 - data/README.md +216 -32
 - data/app/components/react_component.rb +10 -3
 - data/bin/esbuild +0 -0
 - data/bin/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/bin/parcel_css +0 -0
 - 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
         
     |