Skip to content

Serve Vite-based Ember apps from development server - #653

Merged
tricknotes merged 8 commits into
mainfrom
claude/vite-dev-server-hmr-y3xm8i
Sep 4, 2026
Merged

Serve Vite-based Ember apps from development server#653
tricknotes merged 8 commits into
mainfrom
claude/vite-dev-server-hmr-y3xm8i

Conversation

@tricknotes

@tricknotes tricknotes commented Aug 31, 2026

Copy link
Copy Markdown
Owner

Summary

Add support for serving Vite-based Ember applications (generated with ember-cli >= 6.8) from Vite's own development server in development mode, enabling hot module reloading without restarting Rails.

Key Changes

  • New DevServer class (lib/ember_cli/dev_server.rb): Manages the Vite development server lifecycle for a single Ember application

    • Boots the server lazily on first request
    • Detects if a server is already listening and reuses it
    • Provides HTTP methods (get, head, options_request) to fetch assets and HTML from the server
    • Handles timeouts and connection failures with helpful error messages
  • New DevServerProxy Rack middleware (lib/ember_cli/dev_server_proxy.rb): Proxies requests to the development server

    • Forwards GET, HEAD, and OPTIONS requests
    • Strips hop-by-hop headers to avoid connection issues
    • Rejects unsupported HTTP verbs with 405 responses
  • New Deploy::DevServer strategy (lib/ember_cli/deploy/dev_server.rb): Serves applications from the development server

    • Rewrites root-relative URLs in index.html to absolute URLs pointing at the development server
    • Enables Vite's HMR client to connect directly to the development server
  • Enhanced App class (lib/ember_cli/app.rb):

    • Adds dev_server? method to detect when an app should use the development server
    • Adds dev_server property to lazily initialize the DevServer
    • Selects Deploy::DevServer strategy by default for Vite-based apps in development
    • Supports dev_server configuration option to customize host, port, and timeout
  • Enhanced Shell class (lib/ember_cli/shell.rb):

    • Adds start_dev_server method to spawn the Vite development server in a separate process group
    • Adds dev_server_running? method to check server status
    • Properly signals and cleans up the development server process on exit
  • Enhanced Command class (lib/ember_cli/command.rb): Adds dev_server method to construct the Vite CLI command

  • Enhanced PathSet class (lib/ember_cli/path_set.rb): Adds vite property to locate the Vite executable

  • Comprehensive test coverage: Includes unit tests for all new classes and integration tests verifying the full request flow

Notable Implementation Details

  • The development server is started in its own process group, allowing Rails to cleanly shut down the entire server tree on exit
  • Root-relative URLs in index.html are rewritten to absolute URLs so the browser loads Vite's HMR client directly from the development server, not through Rails
  • Assets referenced relatively by the Ember application are still proxied through Rails, maintaining compatibility with the existing asset pipeline
  • The implementation gracefully falls back to reusing an existing server if one is already listening on the configured host and port, enabling manual npm start workflows
  • Configuration is optional; sensible defaults (127.0.0.1, available port, 30-second timeout) are provided

@tricknotes
tricknotes marked this pull request as draft August 31, 2026 12:21
tricknotes pushed a commit to tricknotes/ember-cli-rails-assets that referenced this pull request Sep 2, 2026
`include_ember_script_tags` always read the output of `ember build`, so a Vite-based application served by ember-cli-rails's development server (tricknotes/ember-cli-rails#653) could not use the asset helpers without opting out of the server — and with it, out of fast reloads.

When the application is served by the development server (feature-detected, so older ember-cli-rails releases keep their behavior), read `index.html` from the server instead of a build directory and emit the startup tags with root-relative URLs rewritten to absolute URLs on the server, the same way ember-cli-rails serves the document itself.
Nothing is built, so a change to the application is picked up by reloading the page.

The extraction shared with the build-directory path now also leaves protocol-relative (`//`) URLs alone, and `include_ember_stylesheet_tags` raises its Vite guidance for development-server applications too (their `dist` may not exist, so the Vite detection alone would miss them).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HazNguGSW6gmmb3LwttuUW
@tricknotes
tricknotes force-pushed the claude/vite-dev-server-hmr-y3xm8i branch from 640ce15 to ccd16e5 Compare September 3, 2026 16:06
tricknotes pushed a commit to tricknotes/ember-cli-rails-assets that referenced this pull request Sep 3, 2026
`include_ember_script_tags` always read the output of `ember build`, so a Vite-based application served by ember-cli-rails's development server (tricknotes/ember-cli-rails#653) could not use the asset helpers without opting out of the server — and with it, out of fast reloads.

When the application is served by the development server (feature-detected, so older ember-cli-rails releases keep their behavior), read `index.html` from the server instead of a build directory and emit the startup tags with root-relative URLs rewritten to absolute URLs on the server, the same way ember-cli-rails serves the document itself.
Nothing is built, so a change to the application is picked up by reloading the page.

The extraction shared with the build-directory path now also leaves protocol-relative (`//`) URLs alone, and `include_ember_stylesheet_tags` raises its Vite guidance for development-server applications too (their `dist` may not exist, so the Vite detection alone would miss them).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@tricknotes
tricknotes marked this pull request as ready for review September 3, 2026 18:00
tricknotes and others added 8 commits September 4, 2026 08:07
Vite-based Ember applications (`ember-cli >= 6.8`) were built once,
synchronously, on the first request in `development`, so picking up a
change meant restarting Rails. The classic (Broccoli-based) build avoids
that with `ember build --watch` and `ember-cli-rails-addon`, neither of
which is available to the Vite-based build.

Serve those applications from Vite's own development server instead --
the one the blueprint's `npm start` script runs. `EmberCli::DevServer`
starts it on the first request, waits for it to accept connections, and
signals its process group when Rails exits. If something is already
listening on the configured address, it is used as-is, so a fixed `port`
lets several Rails workers, or a hand-started `npm start`, share one
server.

`EmberCli::Deploy::DevServer` reads `index.html` over HTTP and rewrites
its root-relative `src` and `href` attributes to absolute URLs on the
development server. Loading `@vite/client` from there is what makes the
Vite client open its HMR WebSocket against the development server
directly, so Rails never proxies the socket. Assets the application
references relatively are still requested from Rails, and
`EmberCli::DevServerProxy` forwards them to the development server so
they resolve the same way they do when served out of `dist`.

The strategy is chosen by default only for Vite-based applications in
`development`, and is configured -- or disabled -- with the `dev_server`
option. `test` and `production` continue to be served from the output of
`ember build`, and the classic build is untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A cold development server transforms the application's modules on the first load, which can take longer than Capybara's default 2-second wait on a busy CI runner, failing the examples with an empty page body.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 30-second wait still failed on busy CI runners: a cold development server runs the embroider prebuild and transforms every module on the first load before the application can render anything.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The examples fail intermittently on CI with an empty page body even after a 60-second wait, so the failure is not slowness. Capture the browser console and the document a failing example saw, so that the next occurrence explains why the application did not boot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The dev-server examples failed intermittently with a page that stayed blank for the whole Capybara wait. The browser-state dump showed why: the development server was serving the test-environment config — autoboot: false, rootElement #ember-testing — so Ember loaded but never rendered.

The server shared spec/dummy/my-app with the ember build and ember test runs that other examples perform, and their test-environment prebuild and cache output leaked into the running server depending on example order. Give the server its own copy of the app, made by bin/setup_ember, so the two can no longer interfere.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CI pipes the output through rake, which block-buffers it: a slow or hung job then shows nothing past the seed line, hiding where it stopped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
bin/setup_ember already installs the applications' dependencies (and copies the installed app for the development server), so the rake ember:install that followed only re-ran npm install and npm prune for every registered app. Each of those calls is a no-op that still talks to the registry, and a slow registry then multiplied into jobs hitting the CI runtime cap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ember test occasionally hangs on CI when testem fails to attach to its browser, and the silent hang then eats the whole job's runtime cap. Fail the example after five minutes with a clear error instead — the healthy run takes about half a minute.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@tricknotes
tricknotes force-pushed the claude/vite-dev-server-hmr-y3xm8i branch from c1f47c1 to 5ba5dd3 Compare September 4, 2026 08:08
@tricknotes
tricknotes merged commit 2131f76 into main Sep 4, 2026
11 of 12 checks passed
@tricknotes
tricknotes deleted the claude/vite-dev-server-hmr-y3xm8i branch September 4, 2026 08:20
@tricknotes tricknotes mentioned this pull request Sep 4, 2026
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