# Proscenium - Modern Client-Side Tooling for Rails Proscenium treats your client-side code as first class citizens of your Rails app, and assumes a "fast by default" internet. It compiles your JS, JSX and CSS in real time, and on demand, with no configuration at all! - Zero configuration. - NO JavaScript rumtime needed - just the browser! - Real-time compilation. - No additional process or server - Just run Rails! - Serve assets from anywhere within your Rails root (/app, /config, /lib). - Automatically side load JS/CSS for your layouts and views. - Import JS(X) and CSS from node_modules, URL, local (relative, absolute). - Optional bundling of JS(X) and CSS. - Import Map support for JS and CSS. - CSS Modules. - CSS Custom Media Queries. - CSS mixins. - Minification. - Auto reload after changes (development only). ## !! EXPERIMENTAL SOFTWARE !! While my goal is to use Proscenium in production, I strongly recommended that you **DO NOT** use this in production apps! Right now, this is a play thing, and should only be used for development/testing. ## Installation Add this line to your application's Gemfile, and you're good to go: ```ruby gem 'proscenium' ``` ## Frontend Code Anywhere! Proscenium believes that your frontend code is just as important as your backend code, and is not an afterthought - they should be first class citizens of your Rails app. So instead of throwing all your JS and CSS into a "app/assets" directory, put them wherever you want! For example, if you have JS that is used by your `UsersController#index`, then put it in `app/views/users/index.js`. Or if you have some CSS that is used by your entire application, put it in `app/views/layouts/application.css`. Maybe you have a few JS utility functions, so put them in `lib/utils.js`. Simply create your JS(X) and CSS anywhere you want, and they will be served by your Rails app. Using the examples above... - `app/views/users/index.js` => `https://yourapp.com/app/views/users/index.js` - `app/views/layouts/application.css` => `https://yourapp.com/app/views/layouts/application.css` - `lib/utils.js` => `https://yourapp.com/lib/utils.js` - `config/properties.css` => `https://yourapp.com/config/properties.css` ## Importing Proscenium supports importing JS and CSS from `node_modules`, URL, and local (relative, absolute). Imports are assumed to be JS files, so there is no need to specify the file extesnion in such cases. But you can if you like. All other file types must be specified using their full file name and extension. ### URL Imports Any import beginning with `http://` or `https://` will be fetched from the URL provided: ```js import React from 'https://esm.sh/react` ``` ```css @import 'https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css'; ``` ### Import from NPM (`node_modules`) Bare imports (imports not beginning with `./`, `/`, `https://`, `http://`) are fully supported, and will use your package manager of choice (eg, NPM, Yarn, pnpm): ```js import React from 'react` ``` ### Local Imports And of course you can import your own code, using relative or absolute paths (file extension is optional): ```js /app/views/layouts/application.js import utils from '/lib/utils' ``` ```js /lib/utils.js import constants from './constants' ``` ## Bundling Proscenium does not do any bundling, as we believe that **the web is now fast by default**. So we let you decide if and when to bundle your code using query parameters in your JS and CSS imports. ```js import doStuff from 'stuff?bundle' doStuff() ``` Bundling a URL import is not supported, as the URL itself may also support query parameters, resulting in conflicts. For example, esm.sh also supports a `?bundle` param, bundling a module's dependencies into a single file. Instead, you should install the module locally using your favourite package manager. Note that `?bundle` will only bundle that exact path. It will not bundle any descendant imports. You can bundle all imports within a file by using the `?bundle-all` query string. Use this with caution, as you could end up swallowing everything, resulting in a very large file. ## Import Map Import map for both JS and CSS is supported out of the box, and works with no regard to the browser version being used. This is because the import map is parsed and resolved by Proscenium on the server. Just create `config/import_map.json`: ```json { "imports": { "react": "https://esm.sh/react@18.2.0", "start": "/lib/start.js", "common": "/lib/common.css", "@radix-ui/colors/": "https://esm.sh/@radix-ui/colors@0.1.8/", } } ``` Using the above import map, we can do... ```js import { useCallback } from 'react' import startHere from 'start' import styles from 'common' ``` and for CSS... ```css @import 'common'; @import '@radix-ui/colors/blue.css'; ``` You can instead write your import map in Javascript instead of JSON. So instead of `config/import_map.json`, create `config/import_map.js`, and define an anonymous function. This function accepts a single `environment` argument. ```js env => ({ imports: { react: env === 'development' ? 'https://esm.sh/react@18.2.0?dev' : 'https://esm.sh/react@18.2.0' } }) ``` ### Aliasing You can also use the import map to define aliases: ```json { "imports": { "react": "preact/compact", } } ``` ## Side Loading Proscenium has built in support for automatically side loading JS and CSS with your views and layouts. Just create a JS and/or CSS file with the same name as any view or layout, and make sure your layouts include `<%= side_load_stylesheets %>` and `<%= side_load_javascripts %>`. Something like this: ```html