Skip to content

Own the interpretation of an application's build output - #687

Draft
tricknotes wants to merge 9 commits into
mainfrom
claude/intelligent-goodall-foai0r
Draft

tricknotes wants to merge 9 commits into
mainfrom
claude/intelligent-goodall-foai0r

Conversation

@tricknotes

@tricknotes tricknotes commented Sep 17, 2026 •

Copy link
Copy Markdown
Owner

Why

The asset helpers in ember-cli-rails-assets read a built application to work out what it boots from:
where its assets live, how index.html refers to them, which name package.json gives it, and whether the build is Vite-based.

This gem runs the build, so it already knows all of that.
The two ended up answering the same question in different ways — this gem detects a Vite build from the configuration file at the application's root, while the helpers looked for a module script in the generated index.html.

The dependency also pointed both ways: this gem depended on the helpers, while the helpers called EmberCli::App without declaring that they needed it, and worked around older releases with respond_to?(:dev_server?).

What

Move the interpretation of a build's output here, behind EmberCli::Embedding, the API for embedding an application into a page of its own.
It wraps an application (EmberCli::Embedding.new(EmberCli["frontend"])) and reports:

  • #startup_tags? — whether the application boots from startup tags, as it does when Vite's development server or a Vite build serves it
  • #startup_tags(prepend:) — those tags, read from the development server or the build directory, whichever serves the application
  • #javascript_assets(prepend:) and #stylesheet_assets(prepend:) — the assets a classic build boots from, with prepend joined onto the ones that point into the build

EmberCli::App gains only #vite?, as which build system an application uses is not specific to embedding it.

The classes behind EmberCli::Embedding are private constants of it, so they can change without breaking anyone:

  • AssetMap resolves the references in a classic build's index.html against the files ember build wrote, replacing the helpers' AssetMap, DirectoryAssetMap and Lookup
  • StartupTags extracts the boot tags of a Vite build and joins a prefix onto their root-relative URLs
  • Url recognizes a URL that resolves outside the build, which both of the above leave alone

Because EmberCli::Embedding chooses between the development server and the build directory itself, callers no longer feature-detect the development server.
Because the readers take prepend, callers no longer decide which URLs it applies to.
It reads the build through what EmberCli::App already exposes, such as #dist_path, so nothing else is added to EmberCli::App or EmberCli::PathSet.

The behaviour the helpers gained in tricknotes/ember-cli-rails-assets#43 and #45 comes along:
an asset the build does not produce (a CDN, a font service) is reported as the document refers to it, a tag carrying no URL is skipped, and a reference resolves by the longest trailing path it shares with the build output, so an addon's nested asset resolves to the file it names.

With this knowledge here, the dependency on ember-cli-rails-assets is dropped, and the helpers declare a dependency on this gem instead.
Bundler refuses to resolve a dependency that points both ways, so this direction has to be the one that survives.
nokogiri moves here for the same reason.

The feature scenario that rendered the helpers in a page of its own now embeds the tags from EmberCli::Embedding instead, without the helpers.
It was the only test booting an application from tags embedded into a page, and the only one doing so for a Vite build, so it keeps the contract covered across the whole build and package manager matrix here, where changes to it are made.
Its page moves from /asset-helpers to /embed: mounting at /embedded would name its redirect route embedded, clashing with the route it names.

Compatibility

An application that renders include_ember_script_tags or include_ember_stylesheet_tags now needs gem "ember-cli-rails-assets" in its own Gemfile, where it used to arrive as a dependency of this gem.
render_ember_app and mount_ember_app are unaffected.

UPGRADING.md carries that, and the constants the helpers no longer expose, under a heading for 1.0.0.
The README no longer claims this gem injects the assets itself.

The version is left to the release commit, as RELEASING.md asks.

Testing

  • spec/lib passes, including the EmberCli::Embedding spec, which covers the classes behind it through its public methods and checks that they stay private
  • the embedding page renders the config <meta> tag, the stylesheet and modulepreload links, and the module script of a Vite build from EmberCli::Embedding; booting it in a browser is left to CI, as the local Chrome and ChromeDriver disagree on their version
  • the split was exercised against a real application, with and without the helpers installed

The asset helpers read a built application to work out what it boots from:
where its assets live, how `index.html` refers to them, and whether the
application is Vite-based.
This gem runs the build, so it already knows all of it — it resolves the same
paths and carries a Vite check of its own.

Move that knowledge here, behind an API add-ons can render:

* `EmberCli::App#vite?`
* `EmberCli::App#startup_tags`
* `EmberCli::App#javascript_assets` and `#stylesheet_assets`

`startup_tags` chooses between the development server and the build directory
itself, so callers no longer feature-detect the development server.

With the knowledge here, this gem no longer needs to depend on the helpers, and
the helpers can depend on it instead.
Bundler refuses to resolve a dependency that points both ways, so drop it.
The feature spec that exercised the helpers from here goes with it; the helpers
cover that path in their own suite.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The asset helpers no longer arrive with this gem, so an application that renders
them has to ask for them.
Say so where an upgrading application looks, and correct the README, which still
claimed this gem injects the assets itself.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rendering a layout that calls a missing helper fails with
`ActionView::Template::Error`, raised from the `NoMethodError` behind it.
Nothing else fails, so say that too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tricknotes
tricknotes force-pushed the claude/intelligent-goodall-foai0r branch from 256bb7f to 2e5c502 Compare September 17, 2026 17:23
A built `index.html` may point at assets the build does not produce, such as a
stylesheet served by a font service or a script served by a CDN.
They have no file to resolve against, and an inline `<script>` names no asset at
all, so resolving every reference by its basename fails on both.

Emit a URL that resolves outside the build as it is, and skip a tag that carries
no URL.
`prepend` mounts the build output onto a path or another host, so the readers
take it and join it only onto the assets that point into the build, the way
`startup_tags` already remaps a Vite build.

This carries the behaviour the asset helpers gained in
tricknotes/ember-cli-rails-assets#43 into the code that now interprets the
build, and `StartupTags` shares the predicate that decides it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`ember build` writes what an addon ships in a subdirectory of `assets` into that
subdirectory, and `index.html` refers to it by that path.
Listing the `assets` directory one level deep represented such a file by the
subdirectory that holds it, and matching a reference by its file name alone
resolved it to whichever file happened to share that name.

Represent every file by its path within `assets`, and resolve a reference by the
longest trailing path the document and the build output agree on.
A reference that carries the `assets` directory, or the application's `rootURL`,
still resolves.

This carries what the asset helpers gained in
tricknotes/ember-cli-rails-assets#45 into the code that now interprets the
build.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
What stays is the matching rule the code cannot show on its own, the URLs the
`REMOTE` pattern covers, and why a prefix applies to root-relative URLs only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tricknotes
tricknotes force-pushed the claude/intelligent-goodall-foai0r branch from aa76d07 to 844d7a4 Compare September 18, 2026 01:58
The document wrapped at a column, so a sentence ran across lines and two
sentences shared one.
Give each sentence a line of its own instead, whatever its length.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The scenario that rendered the asset helpers in a page went with the dependency
on them, but it was also the only test that booted an application from tags
embedded into a page of its own, and the only one doing so for a Vite build.

Bring it back without the helpers: the page reads the tags from
`EmberCli::App#startup_tags`, or `#javascript_assets` and `#stylesheet_assets`
for a classic build, the readers add-ons render.
That keeps the reader contract tested across the whole build and package
manager matrix here, where changes to it are made.

The page moves to `/embed`: mounting at `/embedded` names its redirect route
`embedded`, which clashes with the route it names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`EmberCli::App` gained `startup_tags`, `javascript_assets` and `stylesheet_assets` only for code that embeds an application into a page of its own.
Those readers now live on `EmberCli::Embedding`, which wraps an application, so `EmberCli::App` keeps only `vite?` on top of what it already had.

`EmberCli::Embedding#startup_tags?` answers whether the application boots from startup tags, whether Vite's development server or a Vite build serves it, so callers no longer combine `dev_server?` and `vite?` themselves.

`AssetMap`, `StartupTags` and `Url` move under the new class as private constants, leaving one class and four methods to keep compatible.
Their specs now go through `EmberCli::Embedding`.
`PathSet#assets` and `#index_html` go away too, as the build directory is reachable from `EmberCli::App#dist_path`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tricknotes
tricknotes force-pushed the claude/intelligent-goodall-foai0r branch from bceb883 to dff93f5 Compare September 26, 2026 07:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants