{"id":13878337,"url":"https://github.com/joelmoss/proscenium","last_synced_at":"2025-11-11T18:40:21.702Z","repository":{"id":46884444,"uuid":"443631198","full_name":"joelmoss/proscenium","owner":"joelmoss","description":"Modern client-side development for Rails","archived":false,"fork":false,"pushed_at":"2025-11-10T15:20:46.000Z","size":84476,"stargazers_count":104,"open_issues_count":15,"forks_count":5,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-11-10T16:31:56.950Z","etag":null,"topics":["bundler","css","javascript","ruby","ruby-on-rails"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/joelmoss.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-01-01T21:56:27.000Z","updated_at":"2025-11-10T15:20:50.000Z","dependencies_parsed_at":"2023-02-10T06:01:28.541Z","dependency_job_id":"ce47b7f1-6749-431a-9697-f3b043f9f44e","html_url":"https://github.com/joelmoss/proscenium","commit_stats":{"total_commits":239,"total_committers":1,"mean_commits":239.0,"dds":0.0,"last_synced_commit":"44302aa0f04093f70cd8d433bb23db7beabcc3fc"},"previous_names":[],"tags_count":47,"template":false,"template_full_name":null,"purl":"pkg:github/joelmoss/proscenium","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joelmoss%2Fproscenium","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joelmoss%2Fproscenium/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joelmoss%2Fproscenium/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joelmoss%2Fproscenium/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/joelmoss","download_url":"https://codeload.github.com/joelmoss/proscenium/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joelmoss%2Fproscenium/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":283910127,"owners_count":26915128,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-11-11T02:00:06.610Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bundler","css","javascript","ruby","ruby-on-rails"],"created_at":"2024-08-06T08:01:46.713Z","updated_at":"2025-11-11T18:40:21.695Z","avatar_url":"https://github.com/joelmoss.png","language":"Go","readme":"# Proscenium - Integrated Frontend Development for Rails\n\n\u003e 🗣️ prow · see · nee · uhm\n\u003e\n\u003e _noun_: **proscenium**\n\u003e\n\u003e - _the part of a theatre stage in front of the curtain._\n\n**_Proscenium_** treats your frontend and client-side code as first class citizens of your Rails app, and assumes a \"fast by default\" internet. It bundles and minifies JavaScript (+JSX), TypeScript (+TSX) and CSS in real time, on demand, and with zero configuration.\n\n**The highlights:**\n\n- Fast, real-time bundling, tree-shaking, code-splitting and minification of Javascript (.js,.jsx), Typescript (.ts,.tsx) and CSS (.css).\n- NO JavaScript runtime needed (eg. Node) - just the browser!\n- NO build step or pre-compilation.\n- NO additional process or server - Just run `rails server`!\n- Transforms newer JavaScript and CSS syntax to older syntax for older browsers.\n- Deep integration with Rails.\n- Automatically side-load JS and CSS for your layouts, views, and partials.\n- Import from NPM, URL's, and locally.\n- CSS Modules \u0026 mixins.\n- Source maps.\n\n## Table of Contents\n\n- [Getting Started](#getting-started)\n- [Installation](#installation)\n- [Client-Side Code Anywhere](#client-side-code-anywhere)\n- [Side Loading](#side-loading)\n- [Importing](#importing-assets)\n  - [Local Imports](#local-imports)\n- [Source Maps](#source-maps)\n- [SVG](#svg)\n- [Environment Variables](#environment-variables)\n- [i18n](#i18n)\n- [JavaScript](#javascript)\n  - [Tree Shaking](#tree-shaking)\n  - [Code Splitting](#code-splitting)\n  - [JavaScript Caveats](#javascript-caveats)\n- [CSS](#css)\n  - [Importing CSS from JavaScript](#importing-css-from-javascript)\n  - [CSS Modules](#css-modules)\n  - [CSS Mixins](#css-mixins)\n  - [CSS Caveats](#css-caveats)\n- [Typescript](#typescript)\n  - [Typescript Caveats](#typescript-caveats)\n- [JSX](#jsx)\n- [JSON](#json)\n- [rjs is back!](#rjs-is-back)\n- [Resolution](#resolution)\n- [Aliases](#aliases)\n- [Pre-compilation](#precompilation)\n- [Thanks](#thanks)\n- [Development](#development)\n\n## Getting Started\n\nGetting started obviously depends on whether you are adding Proscenium to an existing Rails app, or creating a new one. So choose the appropriate guide below:\n\n- [Getting Started with a new Rails app](https://github.com/joelmoss/proscenium/blob/master/docs/guides/new_rails_app.md)\n- Getting Started with an existing Rails app\n  - [Migrate from Sprockets](docs/guides/migrate_from_sprockets.md)\n  - Migrate from Propshaft _[Coming soon]_\n  - Migrate from Webpacker _[Coming soon]_\n- [Render a React component with Proscenium](docs/guides/basic_react.md)\n\n## Installation\n\nAdd this line to your Rails application's Gemfile, and you're good to go:\n\n```ruby\ngem 'proscenium'\n```\n\nPlease note that Proscenium is designed solely for use with Rails.\n\nNow if you start your Rails app, you can open any front end code (JS, CSS, etc.). For example, a file at `app/assets/stylesheets/application.css` can be accessed at `https://localhost:3000/app/assets/stylesheets/application.css`, which will be transformed, bundled, and minified [in production] in real time.\n\n## Client-Side Code Anywhere\n\nProscenium 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 having to throw all your JS and CSS into a \"app/assets\" directory, and then requiring a separate process to compile or bundle, you can simply put them wherever you want within your app, and just run Rails!\n\nFor example, if you have some JS that is required by your `app/views/users/index.html.erb` view, just create a JS file alongside it at `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` and load it alongside your layout. Maybe you have a few JS utility functions, so put them in `lib/utils.js`.\n\nSimply put your JS(X) and CSS anywhere you want, and they will be served by your Rails app from the same location where you placed them.\n\nUsing the examples above...\n\n- `app/views/users/index.js` =\u003e `https://localhost:3000/app/views/users/index.js`\n- `app/views/layouts/application.css` =\u003e `https://localhost:3000/app/views/layouts/application.css`\n- `lib/utils.js` =\u003e `https://localhost:3000/lib/utils.js`\n- `app/components/menu_component.jsx` =\u003e `https://localhost:3000/app/components/menu_component.jsx`\n- `config/properties.css` =\u003e `https://localhost:3000/config/properties.css`\n\n## Side Loading\n\nProscenium is best experienced when your assets are automatically side loaded.\n\n### The Problem\n\nWith Rails you would typically load your JavaScript and CSS assets declaratively using the `javascript_include_tag` and `stylesheet_link_tag` helpers.\n\nFor example, you may have top-level \"application\" styles located in a file at `/app/assets/stylesheets/application.css`. Likewise, you may have some global JavaScript located in a file at `/app/javascript/application.js`.\n\nYou would manually and declaratively include those two files in each of your layouts, something like this:\n\n```erb\n\u003c%# /app/views/layouts/application.html.erb %\u003e\n\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003ctitle\u003eHello World\u003c/title\u003e\n    \u003c%= stylesheet_link_tag 'application' %\u003e \u003c!-- \u003c\u003c Your app CSS --\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003c%= yield %\u003e\n    \u003c%= javascript_include_tag 'application' %\u003e \u003c!-- \u003c\u003c Your app JS --\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\nNow, you may have some CSS and JavaScript that is only required by a specific view and partial, so you would load that in your view (or layout), something like this:\n\n```erb\n\u003c%# /app/views/users/index.html.erb %\u003e\n\n\u003c%= stylesheet_link_tag 'users' %\u003e\n\u003c%= javascript_include_tag 'users' %\u003e\n\n\u003c%# needed by the `users/_user.html.erb` partial %\u003e\n\u003c%= javascript_include_tag '_user' %\u003e\n\n\u003c% render @users %\u003e\n```\n\nThe main problem is that you have to keep track of all these assets, and make sure each is loaded by all the views that require them, but also avoid loading them when not needed. This can be a real pain, especially when you have a lot of views.\n\n### The Solution\n\nWhen side loading your JavaScript, Typescript and CSS with Proscenium, they are automatically included alongside your views, partials, layouts, and components, and only when needed.\n\nSide loading works by looking for a JS/TS/CSS file with the same name as your view, partial, layout or component. For example, if you have a view at `app/views/users/index.html.erb`, then Proscenium will look for a JS and CSS file at `app/views/users/index.js` (or TypeScript with a .ts extension) and `app/views/users/index.css`. If it finds one, it will automatically include it in the HTML for that view. And only for that view.\n\nThis allows you to keep your assets organized alongside the views, partials, and components that use them, without having to manually track and include them. It also means only the assets that are needed are included.\n\nJSX is also supported for JavaScript and Typescript. Simply use the `.jsx` or `.tsx` extension instead of `.js` or `.ts`.\n\n### Usage\n\nSimply create a JS and/or CSS file with the same name as any view, partial or layout.\n\nLet's continue with our problem example above, where we have the following assets\n\n- `/app/assets/application.css`\n- `/app/assets/application.js`\n- `/app/assets/users.css`\n- `/app/assets/users.js`\n- `/app/assets/user.js`\n\nYour application layout is at `/app/views/layouts/application.hml.erb`, and the view that needs the users assets is at `/app/views/users/index.html.erb`, so move your assets JS and CSS alongside them:\n\n- `/app/views/layouts/application.css`\n- `/app/views/layouts/application.js`\n- `/app/views/users/index.css`\n- `/app/views/users/index.js`\n- `/app/views/users/_user.js` (partial)\n\nNow, in your layout and view, replace the `javascript_include_tag` and `stylesheet_link_tag` helpers with the `include_asset` helper from Proscenium. Something like this:\n\n```erb\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003ctitle\u003eHello World\u003c/title\u003e\n    \u003c%= include_assets # \u003c-- %\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003c%= yield %\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\nOn each page request, Proscenium will check if any of your views, layouts and partials have a JS/TS/CSS file of the same name, and then include them wherever your placed the `include_assets` helper.\n\nNow you never have to remember to include your assets again. Just create them alongside your views, partials and layouts, and Proscenium will take care of the rest.\n\nSide loading is enabled by default, but you can disable it by setting `config.proscenium.side_load` to `false` in your `/config/application.rb`.\n\nThere are also `include_stylesheets` and `include_javascripts` helpers to allow you to control where the CSS and JS assets are included in the HTML. These helpers should be used instead of `include_assets` if you want to control exactly where the assets are included.\n\n## Bundling\n\nTo bundle a file means to inline any imported dependencies into the file itself. This process is recursive so dependencies of dependencies (and so on) will also be inlined.\n\nProscenium will bundle by default, and in real time. So there is no separate build step or pre-compilation.\n\nProscenium supports importing JS, JSX, TS, TSX, CSS and SVG from NPM, by URL, your local app, and even from other Ruby Gems.\n\nBoth static ([`import`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import)) and dynamic ([`import()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import)) imports are supported for JavaScript and TypeScript, and can be used to import JS, TS, JSX, TSX, JSON, CSS and SVG files.\n\nThe [`@import`](https://developer.mozilla.org/en-US/docs/Web/CSS/@import) CSS at-rule is supported for CSS.\n\n### Non-analyzable imports\n\nImport paths are currently only bundled if they are a string literal or a glob pattern. Other forms of import paths are not bundled, and are instead preserved verbatim in the generated output. This is because bundling is a compile-time operation and Proscenium doesn't support all forms of run-time path resolution.\n\nHere are some examples:\n\n```js\n// Analyzable imports (will be bundled)\nimport \"pkg\";\nimport(\"pkg\");\nimport(`./locale-${foo}.json`);\n\n// Non-analyzable imports (will not be bundled)\nimport(`pkg/${foo}`);\n```\n\nThe way to work around non-analyzable imports is to mark the package containing this problematic code as [unbundled](#Unbundling) so that it's not included in the bundle. You will then need to ensure that a copy of the external package is available to your bundled code at run-time.\n\n### Import from NPM (`node_modules`)\n\nBare imports (imports not beginning with `./`, `/`, `https://`, `http://`) are fully supported, and will use your package manager of choice (eg, NPM, Yarn, pnpm) via the `package.json` file located at the root of your Rails app.\n\nInstall the package you want to import using your package manager of choice...\n\n```\nnpm install react\n```\n\n...and then import it as you would any other package.\n\n```js\nimport React from \"react\";\n```\n\n### Local Imports\n\nAnd of course you can import your own code, using relative or absolute paths (file extension is optional, and absolute paths use your Rails root as the base):\n\n```js\nimport utils from \"/lib/utils\";\nimport constants from \"./constants\";\nimport Header from \"/app/components/header\";\n```\n\n```css\n@import \"/lib/reset\";\n```\n\n### Unbundling\n\nSometimes you don't want to bundle an import. For example, you want to ensure that only one instance of React is loaded. In these cases, you can use the `unbundle` import attribute:\n\n```js\nimport React from \"react\" with { unbundle: 'true' };\n```\n\nYou can also unbundle entries in [`aliases`](#aliases) using an `unbundle:` prefix, which ensures that all imports of a particular path are always unbundled:\n\n```ruby\nconfig.proscenium.aliases = {\n  \"react\": \"unbundle:react\"\n}\n```\n\nThen just import as normal:\n\n```js\nimport React from \"react\";\n```\n\nOr if you don't want any bundling at all, simply turn it off application-wide:\n\n```ruby\nconfig.proscenium.bundle = false\n```\n\nThis will mean every asset and import will be loaded independently.\n\n## Source Maps\n\nSource maps can make it easier to debug your code. They encode the information necessary to translate from a line/column offset in a generated output file back to a line/column offset in the corresponding original input file. This is useful if your generated code is sufficiently different from your original code (e.g. your original code is TypeScript or you enabled minification). This is also useful if you prefer looking at individual files in your browser's developer tools instead of one big bundled file.\n\nSource map output is supported for both JavaScript and CSS. Each file is appended with the link to the source map. For example:\n\n```js\n//# sourceMappingURL=/app/views/layouts/application.js.map\n```\n\nYour browsers dev tools should pick this up and automatically load the source map when and where needed.\n\n## SVG\n\nYou can import SVG from JS(X), which will bundle the SVG source code. Additionally, if importing from JSX or TSX, the SVG source code will be rendered as a JSX/TSX component.\n\n## Environment Variables\n\n\u003e Available in `\u003e=0.10.0`\n\nYou can define and access any environment variable from your JavaScript and Typescript under the `proscenium.env` namespace.\n\nFor performance and security reasons you must declare the environment variable names that you wish to expose in your `config/application.rb` file.\n\n```ruby\nconfig.proscenium.env_vars = Set['API_KEY', 'SOME_SECRET_VARIABLE']\nconfig.proscenium.env_vars \u003c\u003c 'ANOTHER_API_KEY'\n```\n\nThis assumes that the environment variable of the same name has already been defined. If not, you will need to define it yourself either in your code using Ruby's `ENV` object, or in your shell.\n\nThese declared environment variables will be replaced with constant expressions, allowing you to use this like this:\n\n```js\nconsole.log(proscenium.env.RAILS_ENV); // console.log(\"development\")\nconsole.log(proscenium.env.RAILS_ENV === \"development\"); // console.log(true)\n```\n\nThe `RAILS_ENV` and `NODE_ENV` environment variables will always automatically be declared for you.\n\nIn addition to this, Proscenium also provides a `process.env.NODE_ENV` variable, which is set to the same value as `proscenium.env.RAILS_ENV`. It is provided to support the community's existing tooling, which often relies on this variable.\n\nEnvironment variables are particularly powerful in aiding [tree shaking](#tree-shaking).\n\n```js\nfunction start() {\n  console.log(\"start\");\n}\nfunction doSomethingDangerous() {\n  console.log(\"resetDatabase\");\n}\n\nproscenium.env.RAILS_ENV === \"development\" \u0026\u0026 doSomethingDangerous();\n\nstart();\n```\n\nIn development the above code will be transformed into the following code, discarding the definition, and call to`doSomethingDangerous()`.\n\n```js\nfunction start() {\n  console.log(\"start\");\n}\nstart();\n```\n\nPlease note that for security reasons environment variables are not replaced in URL imports.\n\nAn undefined environment variable will be replaced with `undefined`.\n\n```js\nconsole.log(proscenium.env.UNKNOWN); // console.log((void 0).UNKNOWN)\n```\n\nThis means that code that relies on this will not be tree shaken. You can work around this by using the [optional chaining operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining):\n\n```js\nif (typeof proscenium.env?.UNKNOWN !== \"undefined\") {\n  // do something if UNKNOWN is defined\n}\n```\n\n## i18n\n\nSupport is provided for importing your Rails locale files from `config/locales/*.yml`, exporting them as JSON.\n\n```js\nimport translations from \"proscenium/i18n\";\n// translations.en.*\n```\n\nIf you have multiple locale files, they will be merged together. into one json object.\n\nNote that because it is assumed that you will be consuming these translations in the browser, all keys are converted to camelCase, as per the JavaScript conventions.\n\n## Javascript\n\nBy default, Proscenium's output will take advantage of all modern JS features from the ES2022 spec and earlier. For example, `a !== void 0 \u0026\u0026 a !== null ? a : b` will become `a ?? b` when minifying (enabled by default in production), which makes use of syntax from the ES2020 version of JavaScript. Any syntax feature that is not supported by ES2020 will be transformed into older JavaScript syntax that is more widely supported.\n\n### Tree Shaking\n\nTree shaking is the term the JavaScript community uses for dead code elimination, a common compiler optimization that automatically removes unreachable code. Tree shaking is enabled by default in Proscenium.\n\n```javascript\nfunction one() {\n  console.log(\"one\");\n}\nfunction two() {\n  console.log(\"two\");\n}\none();\n```\n\nThe above code will be transformed to the following code, discarding `two()`, as it is never called.\n\n```javascript\nfunction one() {\n  console.log(\"one\");\n}\none();\n```\n\n### Code Splitting\n\n[Side loaded](#side-loading) assets are automatically code split. This means that if you have a file that is imported and used imported several times, and by different files, it will be split off into a separate file.\n\nAs an example:\n\n```js\n// /lib/son.js\nimport father from \"./father\";\n\nfather() + \" and Son\";\n```\n\n```js\n// /lib/daughter.js\nimport father from \"./father\";\n\nfather() + \" and Daughter\";\n```\n\n```js\n// /lib/father.js\nexport default () =\u003e \"Father\";\n```\n\nBoth `son.js` and `daughter.js` import `father.js`, so both son and daughter would usually include a copy of father, resulting in duplicated code and larger bundle sizes.\n\nIf these files are side loaded, then `father.js` will be split off into a separate file or chunk, and only downloaded once.\n\n- Code shared between multiple entry points is split off into a separate shared file that both entry points import. That way if the user first browses to one page and then to another page, they don't have to download all of the JavaScript for the second page from scratch if the shared part has already been downloaded and cached by their browser.\n\n- Code referenced through an asynchronous `import()` expression will be split off into a separate file and only loaded when that expression is evaluated. This allows you to improve the initial download time of your app by only downloading the code you need at startup, and then lazily downloading additional code if needed later.\n\n- Without code splitting, an import() expression becomes `Promise.resolve().then(() =\u003e require())` instead. This still preserves the asynchronous semantics of the expression but it means the imported code is included in the same bundle instead of being split off into a separate file.\n\nCode splitting is enabled by default. You can disable it by setting the `code_splitting` configuration option to `false` in your application's `/config/application.rb`:\n\n```ruby\nconfig.proscenium.code_splitting = false\n```\n\n### JavaScript Caveats\n\nThere are a few important caveats as far as JavaScript is concerned. These are [detailed on the esbuild site](https://esbuild.github.io/content-types/#javascript-caveats).\n\n## CSS\n\nCSS is a first-class content type in Proscenium, which means it can bundle CSS files directly without needing to import your CSS from JavaScript code. You can `@import` other CSS files and reference image and font files with `url()` and Proscenium will bundle everything together.\n\nNote that by default, Proscenium's output will take advantage of all modern CSS features. For example, `color: rgba(255, 0, 0, 0.4)` will become `color: #f006` after minifying in production, which makes use of syntax from [CSS Color Module Level 4](https://drafts.csswg.org/css-color-4/#changes-from-3).\n\nThe new CSS nesting syntax is supported, and transformed into non-nested CSS for older browsers.\n\nProscenium will also automatically insert vendor prefixes so that your CSS will work in older browsers.\n\n### Importing CSS from JavaScript\n\nYou can also import CSS from JavaScript. When you do this, Proscenium will automatically append each stylesheet to the document's head as a `\u003clink\u003e` element.\n\n```jsx\nimport \"./button.css\";\n\nexport let Button = ({ text }) =\u003e {\n  return \u003cdiv className=\"button\"\u003e{text}\u003c/div\u003e;\n};\n```\n\n### CSS Modules\n\nProscenium implements a subset of [CSS Modules](https://github.com/css-modules/css-modules). It supports the `:local` and `:global` keywords, but not the `composes` property. (it is recommended that you use mixins instead of `composes`, as they will work everywhere, even in plain CSS files.)\n\nGive any CSS file a `.module.css` extension, and Proscenium will treat it as a CSS Module, transforming all class names with a suffix unique to the file.\n\n```css\n.title {\n  font-size: 20em;\n}\n```\n\nThe above input produces:\n\n```css\n.title-5564cdbb {\n  font-size: 20em;\n}\n```\n\nYou now have a unique class name that you can use pretty much anywhere.\n\n#### In your Views\n\nYou can reference CSS modules from your Rails views, partials, and layouts using the `css_module` helper, which accepts one or more class names, and will return the equivilent CSS module names - the class name with the unique suffix appended.\n\nWith [side-loading](#side-loading) setup, you can use the `css_module` helper as follows.\n\n```erb\n\u003cdiv\u003e\n  \u003ch1 class=\"\u003c%= css_module :hello_title %\u003e\"\u003eHello World\u003c/h1\u003e\n  \u003cp class=\"\u003c%= css_module :body, paragraph: %\u003e\"\u003e\n    Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n  \u003c/p\u003e\n\u003c/div\u003e\n```\n\n`css_module` accepts multiple class names, and will return a space-separated string of transformed CSS module names.\n\n```ruby\ncss_module :my_module_name\n# =\u003e \"my_module_name-ABCD1234\"\n```\n\nYou can even reference a class from any CSS file by passing the URL path to the file, as a prefix to the class name. Doing so will automatically [side load](#side-loading) the stylesheet.\n\n```ruby\ncss_module '/app/components/button.css@big_button'\n# =\u003e \"big_button\"\n```\n\nIt also supports NPM packages (already installed in /node_modules):\n\n```ruby\ncss_module 'mypackage/button@big_button'\n# =\u003e \"big_button\"\n```\n\n`css_module` also accepts a `path` keyword argument, which allows you to specify the path to the CSS\nfile. Note that this will use the given path for all class names passed to that instance of `css_module`.\n\n```ruby\ncss_module :my_module_name, path: Rails.root.join('app/components/button.css')\n```\n\n#### In your JavaScript\n\nImporting a CSS module from JS will automatically append the stylesheet to the document's head. And the result of the import will be an object of CSS class to module names.\n\n```js\nimport styles from \"./styles.module.css\";\n// styles == { header: 'header-5564cdbb' }\n```\n\nIt is important to note that the exported object of CSS module names is actually a JavaScript [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) object. So destructuring the object will not work. Instead, you must access the properties directly.\n\nAlso, importing a CSS module into another CSS module will result in the same digest string for all classes.\n\n### CSS Mixins\n\nProscenium provides functionality for including or \"mixing in\" onr or more CSS classes into another. This is similar to the `composes` property of CSS Modules, but works everywhere, and is not limited to CSS Modules.\n\nCSS mixins are supported using the `@define-mixin` and `@mixin` at-rules.\n\nA mixin is defined using the `@define-mixin` at-rule. Pass it a name, which should adhere to class name semantics, and declare your rules:\n\n```css\n// /lib/mixins.css\n@define-mixin bigText {\n  font-size: 50px;\n}\n```\n\nUse a mixin using the `@mixin` at-rule. Pass it the name of the mixin you want to use, and the url where the mixin is declared. The url is used to resolve the mixin, and can be relative, absolute, a URL, or even from an NPM packacge.\n\n```css\n// /app/views/layouts/application.css\np {\n  @mixin bigText from url(\"/lib/mixins.css\");\n  color: red;\n}\n```\n\nThe above produce this output:\n\n```css\np {\n  font-size: 50px;\n  color: red;\n}\n```\n\nMixins can be declared in any CSS file. They do not need to be declared in the same file as where they are used. however, if you declare and use a mixin in the same file, you don't need to specify the URL of where the mixin is declared.\n\n```css\n@define-mixin bigText {\n  font-size: 50px;\n}\n\np {\n  @mixin bigText;\n  color: red;\n}\n```\n\nCSS modules and Mixins works perfectly together. You can include a mixin in a CSS module.\n\n### CSS Caveats\n\nThere are a few important caveats as far as CSS is concerned. These are [detailed on the esbuild site](https://esbuild.github.io/content-types/#css-caveats).\n\n## Typescript\n\nTypescript and TSX is supported out of the box, and has built-in support for parsing TypeScript syntax and discarding the type annotations. Just rename your files to `.ts` or `.tsx` and you're good to go.\n\nPlease note that Proscenium does not do any type checking so you will still need to run `tsc -noEmit` in parallel with Proscenium to check types.\n\n### Typescript Caveats\n\nThere are a few important caveats as far as Typescript is concerned. These are [detailed on the esbuild site](https://esbuild.github.io/content-types/#typescript-caveats).\n\n## JSX\n\nUsing JSX syntax usually requires you to manually import the JSX library you are using. For example, if you are using React, by default you will need to import React into each JSX file like this:\n\n```javascript\nimport * as React from \"react\";\nrender(\u003cdiv /\u003e);\n```\n\nThis is because the JSX transform turns JSX syntax into a call to `React.createElement` but it does not itself import anything, so the React variable is not automatically present.\n\nProscenium generates these import statements for you. Keep in mind that this also completely changes how the JSX transform works, so it may break your code if you are using a JSX library that is not React.\n\nIn the [not too distant] future, you will be able to configure Proscenium to use a different JSX library, or to disable this auto-import completely.\n\n## JSON\n\nImporting .json files parses the JSON file into a JavaScript object, and exports the object as the default export. Using it looks something like this:\n\n```javascript\nimport object from \"./example.json\";\nconsole.log(object);\n```\n\nIn addition to the default export, there are also named exports for each top-level property in the JSON object. Importing a named export directly means Proscenium can automatically remove unused parts of the JSON file from the bundle, leaving only the named exports that you actually used. For example, this code will only include the version field when bundled:\n\n```javascript\nimport { version } from \"./package.json\";\nconsole.log(version);\n```\n\n## rjs is back\n\nProscenium brings back RJS! Any path ending in .rjs will be served from your Rails app. This allows you to import server rendered javascript.\n\n## Resolution\n\nProscenium will serve files ending with any of these extension: `js,mjs,ts,css,jsx,tsx` from the following directories, and their sub-directories of your Rails application's root: `/app`, `/lib`, `/config`, `/node_modules`, `/vendor`.\n\nSo a file at `/app/views/users/index.js` will be served from `https://localhost:3000/app/views/users/index.js`.\n\nYou can continue to access any file in the `/public` directory as you normally would. Proscenium will not process files in the `/public` directory.\n\nIf requesting a file that exists in a root directory and the public directory, the file in the public directory will be served. For example, if you have a file at `/lib/foo.js` and `/public/lib/foo.js`, and you request `/lib/foo.js`, the file in the public directory (`/public/lib/foo.js`) will be served.\n\n## Aliases\n\nYou can define import aliases via the `config.proscenium.aliases` config option. This allows you to create shorter or more meaningful import paths.\n\n```ruby\nconfig.proscenium.aliases = {\n  \"utils\": \"/lib/utils.js\",\n  \"components\": \"/app/components\"\n}\n```\n\nYou can then import using the alias:\n\n```js\nimport utils from \"utils\";\nimport Header from \"components/header\";\n```\n\n## Pre-compilation\n\nProscenium is designed to bundle and minify your frontend code in real time, on demand, with no build step or pre-compilation needed. However, if you want to pre-compile your assets for production deployment, you can do so using the `assets:precompile` Rake task.\n\n```bash\nrails assets:precompile\n```\n\nBe sure to specify a `Set` of paths which you want to pre-compile via the `config.proscenium.precompile` configuration option. Each path should be a glob pattern that matches the files which are your entry points. Don't include paths that are not entry points. For example:\n\n```ruby\nRails.configuration.proscenium.precompile = Set[\n  \"./app/components/**/*.js\",\n  \"./app/components/**/*.jsx\",\n  \"./app/views/**/*.js\",\n  \"./app/views/**/*.css\",\n  \"./app/views/**/*.module.css\"\n]\n```\n\nThis will bundle, code split, tree shake, and compile all your JS, TS, JSX, TSX and CSS files and place them in the `public/assets` directory, ready to be served in production.\n\n## Thanks\n\nHUGE thanks 🙏 go to [Evan Wallace](https://github.com/evanw) and his amazing [esbuild](https://esbuild.github.io/) project. Proscenium would not be possible without it, and it is esbuild that makes this so fast and efficient.\n\nBecause Proscenium uses esbuild extensively, some of these docs are taken directly from the esbuild docs, with links back to the [esbuild site](https://esbuild.github.io/) where appropriate.\n\n## Development\n\nBefore doing anything else, you will need compile a local version of the Go binary. This is because the Go binary is not checked into the repo. To compile the binary, run:\n\n```bash\nbundle exec rake compile:local\n```\n\n### Running tests\n\nWe have tests for both Ruby and Go. To run the Ruby tests:\n\n```bash\nbin/test\n```\n\nTo run the Go tests:\n\n```bash\ngo test ./test\n```\n\n### Running Go benchmarks\n\n```bash\ngo test ./internal/builder -bench=. -run=\"^$\" -count=10 -benchmem\n```\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at \u003chttps://github.com/joelmoss/proscenium\u003e. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/joelmoss/proscenium/blob/master/CODE_OF_CONDUCT.md).\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n\n## Code of Conduct\n\nEveryone interacting in the Proscenium project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/joelmoss/proscenium/blob/master/CODE_OF_CONDUCT.md).\n","funding_links":[],"categories":["Go"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoelmoss%2Fproscenium","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjoelmoss%2Fproscenium","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoelmoss%2Fproscenium/lists"}