proscenium 0.25.3 → 0.26.1
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 +170 -23
- data/exe/proscenium +19 -0
- data/exe/psm +5 -0
- data/lib/proscenium/app_package.rb +90 -0
- data/lib/proscenium/builder.rb +221 -20
- data/lib/proscenium/bundled_gems.rb +103 -0
- data/lib/proscenium/cli/brackets.rb +66 -0
- data/lib/proscenium/cli/bun_linker.rb +79 -0
- data/lib/proscenium/cli/capabilities.json +29 -0
- data/lib/proscenium/cli/collisions.rb +94 -0
- data/lib/proscenium/cli/contexts.rb +133 -0
- data/lib/proscenium/cli/diff.rb +80 -0
- data/lib/proscenium/cli/error.rb +291 -0
- data/lib/proscenium/cli/gem_archive.rb +44 -0
- data/lib/proscenium/cli/gem_check.rb +150 -0
- data/lib/proscenium/cli/gitignore.rb +63 -0
- data/lib/proscenium/cli/inspect.rb +257 -0
- data/lib/proscenium/cli/install.rb +378 -0
- data/lib/proscenium/cli/manager.rb +323 -0
- data/lib/proscenium/cli/platform_variants.rb +82 -0
- data/lib/proscenium/cli/pnpm_approvals.rb +109 -0
- data/lib/proscenium/cli/pnpm_workspace.rb +149 -0
- data/lib/proscenium/cli/project_lock.rb +106 -0
- data/lib/proscenium/cli/registration.rb +140 -0
- data/lib/proscenium/cli/reporter.rb +103 -0
- data/lib/proscenium/cli/rules.rb +169 -0
- data/lib/proscenium/cli/runner.rb +142 -0
- data/lib/proscenium/cli/verify.rb +107 -0
- data/lib/proscenium/cli.rb +122 -0
- data/lib/proscenium/context_map.rb +181 -0
- data/lib/proscenium/css_module/transformer.rb +18 -3
- data/lib/proscenium/css_module.rb +5 -6
- data/lib/proscenium/dependency_context.rb +74 -0
- data/lib/proscenium/error.rb +10 -0
- data/lib/proscenium/helper.rb +38 -4
- data/lib/proscenium/importer.rb +53 -42
- data/lib/proscenium/manifest.rb +18 -23
- data/lib/proscenium/mapping_generation.rb +64 -0
- data/lib/proscenium/middleware/base.rb +5 -10
- data/lib/proscenium/middleware/chunks.rb +5 -0
- data/lib/proscenium/monkey.rb +51 -69
- data/lib/proscenium/railtie.rb +16 -0
- data/lib/proscenium/railties/assets.rake +2 -1
- data/lib/proscenium/resolver.rb +43 -11
- data/lib/proscenium/runtime/bootstrap.js +26 -6
- data/lib/proscenium/runtime/server.rb +57 -36
- data/lib/proscenium/side_load.rb +75 -18
- data/lib/proscenium/stale_contexts.rb +118 -0
- data/lib/proscenium/templates/rescues/build_error.html.erb +9 -7
- data/lib/proscenium/utils.rb +55 -0
- data/lib/proscenium/version.rb +1 -1
- data/lib/proscenium/yaml_tree.rb +48 -0
- data/lib/proscenium.rb +7 -4
- metadata +42 -13
- data/lib/proscenium/ext/proscenium +0 -0
- data/lib/proscenium/ext/proscenium.h +0 -113
- data/lib/proscenium/react-manager/index.jsx +0 -102
- data/lib/proscenium/react-manager/react.js +0 -2
- data/lib/proscenium/react_componentable.rb +0 -94
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 835847fce3a966a3016be9769010ed3342413a9f2e98ad15d5305ba35681522c
|
|
4
|
+
data.tar.gz: d1fe6d788d29786fa125ec126c5b04416005052b7fc13e5942029b8a75651192
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d033def3b8079bd0fcdc8ed455e31e7edd16e6726a7032d2067730d60822f53e74ec0da111100277e20ae2d801098e15e5408547ae30f27b961a8d3512809056
|
|
7
|
+
data.tar.gz: ee6ce7c714d17cb1d16eb682b0d854a7a8f4b0620b4063939c27b1ace672abfc1728884e6ffe803ed65bcd2843dafc9c611e3958c0f9e9b647e32648ee0e68e5
|
data/README.md
CHANGED
|
@@ -25,10 +25,14 @@
|
|
|
25
25
|
|
|
26
26
|
- [Getting Started](#getting-started)
|
|
27
27
|
- [Installation](#installation)
|
|
28
|
+
- [Supported platforms](#supported-platforms)
|
|
28
29
|
- [Client-Side Code Anywhere](#client-side-code-anywhere)
|
|
29
30
|
- [Side Loading](#side-loading)
|
|
30
|
-
- [
|
|
31
|
+
- [Bundling](#bundling)
|
|
32
|
+
- [Import from NPM (`node_modules`)](#import-from-npm-node_modules)
|
|
33
|
+
- [RubyGem NPM dependencies](#rubygem-npm-dependencies)
|
|
31
34
|
- [Local Imports](#local-imports)
|
|
35
|
+
- [Unbundling](#unbundling)
|
|
32
36
|
- [Source Maps](#source-maps)
|
|
33
37
|
- [SVG](#svg)
|
|
34
38
|
- [Environment Variables](#environment-variables)
|
|
@@ -51,7 +55,7 @@
|
|
|
51
55
|
- [Testing your JavaScript](#testing-your-javascript)
|
|
52
56
|
- [Resolution](#resolution)
|
|
53
57
|
- [Aliases](#aliases)
|
|
54
|
-
- [Pre-compilation](#
|
|
58
|
+
- [Pre-compilation](#pre-compilation)
|
|
55
59
|
- [Puma `preload_app!` and Cluster Mode](#puma-preload_app-and-cluster-mode)
|
|
56
60
|
- [Thanks](#thanks)
|
|
57
61
|
- [Development](#development)
|
|
@@ -63,7 +67,8 @@ Getting started obviously depends on whether you are adding Proscenium to an exi
|
|
|
63
67
|
- [Getting Started with a new Rails app](docs/guides/new_rails_app.md)
|
|
64
68
|
- Getting Started with an existing Rails app
|
|
65
69
|
- [Migrate from Sprockets](docs/guides/migrate_from_sprockets.md)
|
|
66
|
-
- [
|
|
70
|
+
- [RubyGem NPM dependencies](docs/guides/rubygem_npm_dependencies.md)
|
|
71
|
+
- [Shipping NPM dependencies in a gem](docs/guides/gem_author.md)
|
|
67
72
|
|
|
68
73
|
## Installation
|
|
69
74
|
|
|
@@ -73,10 +78,69 @@ Add this line to your Rails application's Gemfile, and you're good to go:
|
|
|
73
78
|
gem 'proscenium'
|
|
74
79
|
```
|
|
75
80
|
|
|
76
|
-
Please note that Proscenium is designed solely for use with Rails.
|
|
81
|
+
Please note that Proscenium is designed solely for use with Rails. It needs Ruby 3.4 or newer, and
|
|
82
|
+
Rails 7.2 to 8.x.
|
|
77
83
|
|
|
78
84
|
Now 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.
|
|
79
85
|
|
|
86
|
+
### Supported platforms
|
|
87
|
+
|
|
88
|
+
Proscenium is a Ruby gem wrapped around a Go library, so it ships a precompiled gem per platform.
|
|
89
|
+
Bundler picks the right one for you.
|
|
90
|
+
|
|
91
|
+
| Platform | Gem |
|
|
92
|
+
| --- | --- |
|
|
93
|
+
| macOS, Apple silicon | `arm64-darwin` |
|
|
94
|
+
| macOS, Intel | `x86_64-darwin` |
|
|
95
|
+
| Linux glibc, arm64 | `aarch64-linux-gnu` |
|
|
96
|
+
| Linux glibc, x86-64 | `x86_64-linux-gnu` |
|
|
97
|
+
| Windows, x64 ([RubyInstaller](https://rubyinstaller.org/)) | `x64-mingw-ucrt` |
|
|
98
|
+
|
|
99
|
+
The Linux gems need **RubyGems 3.3.22 or newer**, which is the first version able to tell
|
|
100
|
+
`-gnu` from `-musl`. Older versions cannot, so they refuse the gem rather than installing one your
|
|
101
|
+
machine cannot load. Ruby 3.4 already ships a newer RubyGems than this, so you are almost certainly
|
|
102
|
+
fine; if not, `gem update --system`. Bundler 2.5.6 or newer resolves the platform names without
|
|
103
|
+
needing anything else from you.
|
|
104
|
+
|
|
105
|
+
Installing on a platform not listed here gets you the platform-less gem, which carries no compiled
|
|
106
|
+
library, and Proscenium will say so by name when it loads rather than failing somewhere inside FFI.
|
|
107
|
+
If your platform should be supported, please [open an
|
|
108
|
+
issue](https://github.com/joelmoss/proscenium/issues).
|
|
109
|
+
|
|
110
|
+
The Linux gems name their libc. Earlier releases shipped bare `x86_64-linux` and `aarch64-linux`
|
|
111
|
+
names, which RubyGems matches on glibc and musl alike. If your `Gemfile.lock` is pinned to either,
|
|
112
|
+
add the matching `-gnu` platform:
|
|
113
|
+
|
|
114
|
+
```sh
|
|
115
|
+
bundle lock --add-platform x86_64-linux-gnu # or aarch64-linux-gnu, on ARM
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Adding it is enough. There is no need to remove the old platform.
|
|
119
|
+
|
|
120
|
+
On Windows, with a `Gemfile.lock` written on another machine: a plain `bundle install` adds the
|
|
121
|
+
platform itself, but in frozen or deployment mode Bundler will not, so add it once.
|
|
122
|
+
|
|
123
|
+
```sh
|
|
124
|
+
bundle lock --add-platform x64-mingw-ucrt
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Alpine and other musl Linux distributions are not supported
|
|
128
|
+
|
|
129
|
+
Use a glibc base image, such as `ruby:3.4-slim` rather than `ruby:3.4-alpine`.
|
|
130
|
+
|
|
131
|
+
This is not an oversight. Proscenium's Go library is built with `-buildmode=c-shared` and loaded
|
|
132
|
+
at runtime through Ruby's FFI, which uses `dlopen`. Go emits initial-exec TLS relocations into
|
|
133
|
+
such libraries, and musl refuses to `dlopen` anything carrying them, by design:
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
Error relocating ...: free: initial-exec TLS resolves to dynamic definition
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The library builds and links against musl perfectly well; it just cannot be loaded the way
|
|
140
|
+
Proscenium needs to load it. This is [golang/go#54805](https://github.com/golang/go/issues/54805),
|
|
141
|
+
open since 2022, and the linker flag that resolves it has not shipped as of Go 1.27. Musl gems
|
|
142
|
+
will follow when it does.
|
|
143
|
+
|
|
80
144
|
## Client-Side Code Anywhere
|
|
81
145
|
|
|
82
146
|
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 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!
|
|
@@ -182,7 +246,7 @@ Now, in your layout and view, replace the `javascript_include_tag` and `styleshe
|
|
|
182
246
|
</html>
|
|
183
247
|
```
|
|
184
248
|
|
|
185
|
-
On 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
|
|
249
|
+
On 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 you placed the `include_assets` helper.
|
|
186
250
|
|
|
187
251
|
Now 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.
|
|
188
252
|
|
|
@@ -190,6 +254,31 @@ Side loading is enabled by default, but you can disable it by setting `config.pr
|
|
|
190
254
|
|
|
191
255
|
There 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.
|
|
192
256
|
|
|
257
|
+
### Controlling side loading
|
|
258
|
+
|
|
259
|
+
`sideload_assets` turns side loading off, or sets attributes on the tags it adds. In a controller, it applies to every view, layout and partial that controller renders. Each of these is an alternative:
|
|
260
|
+
|
|
261
|
+
```ruby
|
|
262
|
+
class UsersController < ApplicationController
|
|
263
|
+
sideload_assets false # side load nothing
|
|
264
|
+
sideload_assets css: false # side load JS only
|
|
265
|
+
sideload_assets js: { defer: true } # add attributes to each script tag
|
|
266
|
+
sideload_assets proc { !request.xhr? } # evaluated against the controller, on every request
|
|
267
|
+
end
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
In a view, layout or partial, it applies to that template, for that render only:
|
|
271
|
+
|
|
272
|
+
```erb
|
|
273
|
+
<% sideload_assets false %>
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Call it outside any `cache` block. A cache hit skips the block, and the call with it, so that render side loads the template's assets as though it had not been called.
|
|
277
|
+
|
|
278
|
+
A partial rendered as a collection is side loaded once for the whole collection, so a `sideload_assets` call in any item applies to every item. With `cached: true`, a cache hit renders no items, so the call never runs. To control a cached collection partial, call `sideload_assets` in the controller instead.
|
|
279
|
+
|
|
280
|
+
In a partial rendered with a block, as in `render 'box' do ... end`, a call in the partial applies to the partial, and a call inside the block applies to the template that passed it.
|
|
281
|
+
|
|
193
282
|
## Bundling
|
|
194
283
|
|
|
195
284
|
To 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.
|
|
@@ -218,7 +307,7 @@ import(`./locale-${foo}.json`);
|
|
|
218
307
|
import(`pkg/${foo}`);
|
|
219
308
|
```
|
|
220
309
|
|
|
221
|
-
The way to work around non-analyzable imports is to mark the package containing this problematic code as [unbundled](#
|
|
310
|
+
The 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.
|
|
222
311
|
|
|
223
312
|
### Import from NPM (`node_modules`)
|
|
224
313
|
|
|
@@ -236,6 +325,32 @@ npm install react
|
|
|
236
325
|
import React from "react";
|
|
237
326
|
```
|
|
238
327
|
|
|
328
|
+
### RubyGem NPM dependencies
|
|
329
|
+
|
|
330
|
+
A gem can ship frontend code that imports npm packages, declared in a package.json of its own. Add
|
|
331
|
+
the gem with Bundler, then install its NPM dependencies with your app's package manager,
|
|
332
|
+
pnpm or Bun:
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
bundle exec proscenium install
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
That writes a small package.json for each such gem under `.proscenium/packages/`, holding only the
|
|
339
|
+
gem's dependencies, registers them with your package manager, and runs its install. Commit what it
|
|
340
|
+
lists. The gem's own files stay where Bundler put them. Run it again after every Gemfile change, and
|
|
341
|
+
in CI run `bundle exec proscenium install --frozen`, which changes nothing you commit and fails if
|
|
342
|
+
anything is out of date. When something is not working, `bundle exec proscenium doctor` (also
|
|
343
|
+
`inspect`) lists every problem it finds, each with its fix. `psm` is a shorter name for the
|
|
344
|
+
same command: `bundle exec psm install`.
|
|
345
|
+
|
|
346
|
+
Its install covers your app's own packages too, so it replaces `pnpm install` or `bun install`
|
|
347
|
+
everywhere you ran them, and `install --frozen` replaces a frozen install. It does not add, remove
|
|
348
|
+
or upgrade packages: for your app's own dependencies, keep using `pnpm add`, `pnpm remove` and
|
|
349
|
+
`pnpm update`, or `bun add`, `bun remove` and `bun update`.
|
|
350
|
+
|
|
351
|
+
See [RubyGem NPM dependencies](docs/guides/rubygem_npm_dependencies.md) for the details,
|
|
352
|
+
and the [gem author guide](docs/guides/gem_author.md) if you are writing such a gem.
|
|
353
|
+
|
|
239
354
|
### Local Imports
|
|
240
355
|
|
|
241
356
|
And 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):
|
|
@@ -296,6 +411,10 @@ Your browsers dev tools should pick this up and automatically load the source ma
|
|
|
296
411
|
|
|
297
412
|
You 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.
|
|
298
413
|
|
|
414
|
+
That component is compiled with esbuild's default JSX runtime, React, even when a `tsconfig.json` or `jsconfig.json` names another `jsxImportSource`, so importing an SVG from JSX needs `react` installed.
|
|
415
|
+
|
|
416
|
+
When bundling, the SVG is read as markup, never as code, whether it is local or imported by URL. Its text and attribute values are kept as plain strings, so an expression such as `{proscenium.env.API_KEY}` inside an SVG stays literal text, and anything after the root element is ignored. The SVG is still rendered into the page as-is, so only import SVGs from sources you trust.
|
|
417
|
+
|
|
299
418
|
## Environment Variables
|
|
300
419
|
|
|
301
420
|
You can define and access any environment variable from your JavaScript and Typescript under the `proscenium.env` namespace.
|
|
@@ -303,10 +422,13 @@ You can define and access any environment variable from your JavaScript and Type
|
|
|
303
422
|
For performance and security reasons you must declare the environment variable names that you wish to expose in your `config/application.rb` file.
|
|
304
423
|
|
|
305
424
|
```ruby
|
|
306
|
-
config.proscenium.env_vars = Set['
|
|
307
|
-
config.proscenium.env_vars << '
|
|
425
|
+
config.proscenium.env_vars = Set['APP_VERSION', 'PUBLIC_API_URL']
|
|
426
|
+
config.proscenium.env_vars << 'SENTRY_DSN'
|
|
308
427
|
```
|
|
309
428
|
|
|
429
|
+
> [!WARNING]
|
|
430
|
+
> A declared variable's value is written as a plain string into any JavaScript that references it, both in what Proscenium serves and in what `assets:precompile` writes under `public/`. Anyone who loads the page can read it. Never declare a secret, such as an API key, password or token.
|
|
431
|
+
|
|
310
432
|
This 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.
|
|
311
433
|
|
|
312
434
|
These declared environment variables will be replaced with constant expressions, allowing you to use this like this:
|
|
@@ -335,7 +457,7 @@ proscenium.env.RAILS_ENV === "development" && doSomethingDangerous();
|
|
|
335
457
|
start();
|
|
336
458
|
```
|
|
337
459
|
|
|
338
|
-
In development the above code will be transformed into the following code, discarding the definition, and call to`doSomethingDangerous()`.
|
|
460
|
+
In any environment other than development, such as production, the above code will be transformed into the following code, discarding the definition of, and call to, `doSomethingDangerous()`.
|
|
339
461
|
|
|
340
462
|
```js
|
|
341
463
|
function start() {
|
|
@@ -371,6 +493,10 @@ import translations from "proscenium/i18n";
|
|
|
371
493
|
|
|
372
494
|
If you have multiple locale files, they will be merged together. into one json object.
|
|
373
495
|
|
|
496
|
+
An app with no `config/locales` directory exports an empty object. A directory that exists but
|
|
497
|
+
cannot be read - wrong permissions, an I/O error - fails the build instead, because the alternative
|
|
498
|
+
is an app that silently ships with every translation missing.
|
|
499
|
+
|
|
374
500
|
Note 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.
|
|
375
501
|
|
|
376
502
|
## Javascript
|
|
@@ -520,13 +646,13 @@ With [side-loading](#side-loading) setup, you can use the `css_module` helper as
|
|
|
520
646
|
```erb
|
|
521
647
|
<div>
|
|
522
648
|
<h1 class="<%= css_module :hello_title %>">Hello World</h1>
|
|
523
|
-
<p class="<%= css_module :body, paragraph
|
|
649
|
+
<p class="<%= css_module :body, :paragraph %>">
|
|
524
650
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
|
525
651
|
</p>
|
|
526
652
|
</div>
|
|
527
653
|
```
|
|
528
654
|
|
|
529
|
-
`css_module` accepts multiple class names, and will return a space-separated string of transformed CSS module names.
|
|
655
|
+
`css_module` accepts multiple class names, and will return a space-separated string of transformed CSS module names. Arrays of names are flattened, and `nil`, `false` and blank names are ignored, so `css_module :card, (active? && :active)` works.
|
|
530
656
|
|
|
531
657
|
```ruby
|
|
532
658
|
css_module :my_module_name
|
|
@@ -614,6 +740,12 @@ p {
|
|
|
614
740
|
}
|
|
615
741
|
```
|
|
616
742
|
|
|
743
|
+
A mixin must be defined at the root of a file. A `@define-mixin` inside a rule or an at-rule block such as `@media`, or inside another mixin, does not define anything: it is passed through as written, with a warning.
|
|
744
|
+
|
|
745
|
+
One stylesheet may expand at most 100,000 mixins, inserting at most 4 MiB (4,194,304 bytes) of mixin definitions, with mixins nested at most 100 deep, so a chain of mixins that each include another more than once cannot grow without bound. Past any limit, each remaining `@mixin` is left as written, with one warning.
|
|
746
|
+
|
|
747
|
+
Each stylesheet reports at most 100 warnings, plus one saying how many more were not reported and, past a limit, the mixin limit warning. A precompile sends at most 100 errors and 100 warnings in all, then one more message for each saying how many more there were, so in a build that noisy the limit warning can be one of those counted.
|
|
748
|
+
|
|
617
749
|
CSS modules and Mixins works perfectly together. You can include a mixin in a CSS module.
|
|
618
750
|
|
|
619
751
|
### CSS Caveats
|
|
@@ -632,18 +764,25 @@ There are a few important caveats as far as Typescript is concerned. These are [
|
|
|
632
764
|
|
|
633
765
|
## JSX
|
|
634
766
|
|
|
635
|
-
|
|
767
|
+
JSX needs no imports. Proscenium uses esbuild's automatic JSX runtime, which imports what each JSX file needs for you, from React's runtime (`react/jsx-runtime`) by default.
|
|
636
768
|
|
|
637
|
-
|
|
638
|
-
import * as React from "react";
|
|
639
|
-
render(<div />);
|
|
640
|
-
```
|
|
769
|
+
To use another JSX library, such as Preact, set `jsxImportSource` in a `tsconfig.json` or `jsconfig.json`. It applies to every file in that directory and below:
|
|
641
770
|
|
|
642
|
-
|
|
771
|
+
```json
|
|
772
|
+
{
|
|
773
|
+
"compilerOptions": {
|
|
774
|
+
"jsx": "react-jsx",
|
|
775
|
+
"jsxImportSource": "preact"
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
```
|
|
643
779
|
|
|
644
|
-
|
|
780
|
+
Or set it for one file with a comment at the top:
|
|
645
781
|
|
|
646
|
-
|
|
782
|
+
```jsx
|
|
783
|
+
/** @jsxImportSource preact */
|
|
784
|
+
export default () => <div>Hello</div>;
|
|
785
|
+
```
|
|
647
786
|
|
|
648
787
|
## JSON
|
|
649
788
|
|
|
@@ -763,8 +902,8 @@ treated as test, and so is served and precompiled unminified.
|
|
|
763
902
|
- **Stack traces name your functions, not your source lines.** Output is only minified in
|
|
764
903
|
production, so a failure points at a real function name and a real line of the built module.
|
|
765
904
|
It is not mapped back to the original file: a source map is embedded in everything the harness
|
|
766
|
-
builds, but Bun does not apply one to a module a plugin loaded (measured on Bun 1.3.13
|
|
767
|
-
costs nothing to carry, so it stays for when Bun does.
|
|
905
|
+
builds, but Bun does not apply one to a module a plugin loaded (measured on Bun 1.3.13 and
|
|
906
|
+
1.4.2). The map costs nothing to carry, so it stays for when Bun does.
|
|
768
907
|
|
|
769
908
|
- **Node, Deno and Vitest are not supported yet.** They can reuse the same daemon; see `TODOS.md`.
|
|
770
909
|
|
|
@@ -804,6 +943,8 @@ Proscenium is designed to bundle and minify your frontend code in real time, on
|
|
|
804
943
|
rails assets:precompile
|
|
805
944
|
```
|
|
806
945
|
|
|
946
|
+
If any entry point fails to build, the task raises `Proscenium::Builder::CompileError` with esbuild's messages.
|
|
947
|
+
|
|
807
948
|
Be 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:
|
|
808
949
|
|
|
809
950
|
```ruby
|
|
@@ -851,13 +992,19 @@ bin/test
|
|
|
851
992
|
To run the Go tests:
|
|
852
993
|
|
|
853
994
|
```bash
|
|
854
|
-
go test ./test
|
|
995
|
+
go test ./test ./internal/...
|
|
996
|
+
```
|
|
997
|
+
|
|
998
|
+
To run the JavaScript tests, which need the Go library compiled first:
|
|
999
|
+
|
|
1000
|
+
```bash
|
|
1001
|
+
cd fixtures/dummy && bun test test/js/
|
|
855
1002
|
```
|
|
856
1003
|
|
|
857
1004
|
### Running Go benchmarks
|
|
858
1005
|
|
|
859
1006
|
```bash
|
|
860
|
-
go test ./
|
|
1007
|
+
go test ./test -bench=. -run="^$" -count=10 -benchmem
|
|
861
1008
|
```
|
|
862
1009
|
|
|
863
1010
|
## Contributing
|
data/exe/proscenium
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Run as `bundle exec proscenium`. Loads the CLI alone: never `proscenium` itself, Rails, FFI or
|
|
5
|
+
# the engine library (see lib/proscenium/cli.rb).
|
|
6
|
+
# When the CLI started loading, for the timings install reports (C40).
|
|
7
|
+
PROSCENIUM_STARTED = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
8
|
+
|
|
9
|
+
# Every file the CLI reads (package.json, contexts, pnpm-workspace.yaml) is UTF-8, whatever the
|
|
10
|
+
# locale: under LANG=C, as minimal Docker and CI images run, Ruby would read them as US-ASCII.
|
|
11
|
+
# Ruby warns of the setting, which is meant for this process alone.
|
|
12
|
+
verbose = $VERBOSE
|
|
13
|
+
$VERBOSE = nil
|
|
14
|
+
Encoding.default_external = Encoding::UTF_8
|
|
15
|
+
$VERBOSE = verbose
|
|
16
|
+
|
|
17
|
+
require 'proscenium/cli'
|
|
18
|
+
|
|
19
|
+
exit Proscenium::CLI.start(ARGV)
|
data/exe/psm
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module Proscenium
|
|
6
|
+
# What the app's own package.json says that the context map needs (#154). Like ContextMap, which
|
|
7
|
+
# it reads JSON through, it loads nothing the `proscenium` CLI may not.
|
|
8
|
+
module AppPackage
|
|
9
|
+
LOCAL_SPECS = %w[link: file: workspace: portal:].freeze
|
|
10
|
+
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
# The names of the app's own `link:`, `file:` and workspace dependencies, peers included, as
|
|
14
|
+
# pnpm installs those too, and in a Bun app any workspace member it depends on by version,
|
|
15
|
+
# which Bun links. They keep their link paths once dependency contexts make other packages
|
|
16
|
+
# real-path.
|
|
17
|
+
def local_packages(root)
|
|
18
|
+
package = read(root) or return []
|
|
19
|
+
|
|
20
|
+
members = bun?(root, package) ? workspace_members(root, package['workspaces']) : []
|
|
21
|
+
%w[dependencies devDependencies optionalDependencies peerDependencies].flat_map do |field|
|
|
22
|
+
deps = package[field]
|
|
23
|
+
next [] unless deps.is_a?(Hash)
|
|
24
|
+
|
|
25
|
+
deps.select { |name, spec| spec.to_s.start_with?(*LOCAL_SPECS) || members.include?(name) }
|
|
26
|
+
.keys
|
|
27
|
+
end.uniq.sort
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Whether Bun installs the app: packageManager names it, or else its lockfile is there.
|
|
31
|
+
def bun?(root, package)
|
|
32
|
+
named = package['packageManager']
|
|
33
|
+
return named.split('@').first == 'bun' if named.is_a?(String)
|
|
34
|
+
|
|
35
|
+
%w[bun.lock bun.lockb].any? { File.exist?(File.join(root, it)) }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# The names of the packages `workspaces` (an array, or an object's `packages`) takes in, less
|
|
39
|
+
# those a `!` pattern leaves out, read as Bun's glob does: braces and `**` included.
|
|
40
|
+
def workspace_members(root, workspaces)
|
|
41
|
+
patterns = Array(workspaces.is_a?(Hash) ? workspaces['packages'] : workspaces).grep(String)
|
|
42
|
+
excluded, included = patterns.map { it.delete_prefix('./').chomp('/') }
|
|
43
|
+
.partition { it.start_with?('!') }
|
|
44
|
+
dirs = included.flat_map { Dir.glob(it, File::FNM_EXTGLOB, base: root) }.uniq
|
|
45
|
+
dirs.reject { |dir| excluded.any? { File.fnmatch?(it[1..], dir, File::FNM_EXTGLOB) } }
|
|
46
|
+
.filter_map { read(File.join(root, it))&.dig('name') }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Whether package.json's text, for a file JSON.parse refuses, registers the contexts in its
|
|
50
|
+
# `workspaces`, decoding JSON's `\/` and `\uXXXX`
|
|
51
|
+
# escapes in the key and the pattern, as Bun does.
|
|
52
|
+
def workspaces?(text) = unescape(text).match?(WORKSPACES)
|
|
53
|
+
|
|
54
|
+
# JSON text with its `\uXXXX` and `\/` escapes decoded, as Bun reads them, for matching a file
|
|
55
|
+
# JSON.parse refuses.
|
|
56
|
+
# A surrogate pair is one character; a lone half, which JSON allows, becomes U+FFFD rather
|
|
57
|
+
# than raising.
|
|
58
|
+
def unescape(text)
|
|
59
|
+
text.gsub(ESCAPE) do
|
|
60
|
+
high, low, single = ::Regexp.last_match.captures
|
|
61
|
+
if high then (0x10000 + ((high.hex - 0xD800) << 10) + (low.hex - 0xDC00)).chr(Encoding::UTF_8)
|
|
62
|
+
elsif single then SURROGATES.cover?(single.hex) ? "\uFFFD" : single.hex.chr(Encoding::UTF_8)
|
|
63
|
+
else '/'
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
ESCAPE = %r{\\u([dD][89abAB]\h{2})\\u([dD][c-fC-F]\h{2})|\\u(\h{4})|\\/}
|
|
69
|
+
SURROGATES = (0xD800..0xDFFF)
|
|
70
|
+
|
|
71
|
+
# The pattern inside package.json's `workspaces`, an array or an object's `packages`.
|
|
72
|
+
WORKSPACES = /"workspaces"\s*:\s*(?:\{[^}]*"packages"\s*:\s*)?\[[^\]]*
|
|
73
|
+
"\.proscenium\/packages\/\*"/x
|
|
74
|
+
|
|
75
|
+
# The manager `packageManager` names, or nil.
|
|
76
|
+
def package_manager(root)
|
|
77
|
+
field = read(root)&.dig('packageManager')
|
|
78
|
+
field.split('@').first if field.is_a?(String)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# The app's package.json as an object, or nil when it is missing or not one.
|
|
82
|
+
def read(root)
|
|
83
|
+
path = File.join(root, 'package.json')
|
|
84
|
+
package = File.exist?(path) && ContextMap.read_json(path)
|
|
85
|
+
package if package.is_a?(Hash)
|
|
86
|
+
rescue JSON::ParserError
|
|
87
|
+
nil
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|