Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -568,8 +568,14 @@ end
Rendering EmberCLI applications with `render_ember_app` is the recommended,
actively supported method of serving EmberCLI applications.

However, for the sake of backwards compatibility, `ember-cli-rails` supports
injecting the EmberCLI-generated assets into an existing Rails layout.
However, for the sake of backwards compatibility, the EmberCLI-generated assets
can be injected into an existing Rails layout.
The helpers that do that ship in [ember-cli-rails-assets], so add it to the
`Gemfile` alongside this gem:

```ruby
gem "ember-cli-rails-assets"
```

**Note:** for Vite-based applications (generated with `ember-cli >= 6.8`),
use `include_ember_script_tags` on its own. It emits everything the
Expand Down
115 changes: 57 additions & 58 deletions UPGRADING.md
Original file line number Diff line number Diff line change
@@ -1,48 +1,58 @@
# Asset helpers

From `ember-cli-rails@1.0.0` and on, the asset helpers are no longer installed alongside this gem.

`ember-cli-rails` interprets a built Ember application and serves it; `ember-cli-rails-assets` renders what it reports into an existing Rails layout.
Those are separate jobs, and this gem no longer depends on the other to do them, so an application that renders `include_ember_script_tags` or `include_ember_stylesheet_tags` has to ask for the helpers itself:

```ruby
# Gemfile

gem "ember-cli-rails"
gem "ember-cli-rails-assets"
```

Without that line the helpers are undefined, and rendering a layout that calls one fails with the `ActionView::Template::Error` raised from its `NoMethodError`.
Everything else keeps working, so only the layouts that call the helpers break.

An application that serves its Ember applications with `render_ember_app`, or mounts them with `mount_ember_app`, needs no change.
That is the recommended way to serve an EmberCLI application, and it has never involved the helpers.

The helpers used to work out for themselves what a built application boots from.
`ember-cli-rails` reports that now, through `EmberCli::Embedding`, which wraps an application (`EmberCli::Embedding.new(EmberCli["frontend"])`) to embed it into a page of your own.
Code that called into the helpers' internals calls this gem instead:

* `EmberCli::Assets::Paths#vite?` is `EmberCli::App#vite?`
* `EmberCli::Assets::Lookup#javascript_assets` and `#stylesheet_assets` are `EmberCli::Embedding#javascript_assets` and `#stylesheet_assets`, which take the `prepend:` the helpers used to join on themselves, and join it only onto the assets that point into the build
* `EmberCli::Assets::AssetMap`, `EmberCli::Assets::DirectoryAssetMap` and `EmberCli::Assets::Url` have no replacement: `EmberCli::Embedding` reads the build with classes it keeps to itself
* `EmberCli::Assets::BuildError` is `EmberCli::BuildError`, so rescue that instead

# EmberCLI support

`ember-cli >= 6.8` generates applications that are built with [Vite] instead
of the classic Broccoli-based pipeline. `ember-cli-rails` supports both build
systems, and detects the Vite-based build by the presence of a `vite.config.*`
file in the Ember application's root.

When upgrading an Ember application to the Vite-based blueprint, note the
following differences in how `ember-cli-rails` treats it:

* Remove `ember-cli-rails-addon` from the application's `package.json`. The
addon is incompatible with the Vite-based build (it forces
`storeConfigInMeta` off and ships an initializer that imports the removed
`ember` module), and `ember-cli-rails` no longer needs it there.
* Use `include_ember_script_tags` on its own instead of pairing it with
`include_ember_stylesheet_tags`: for Vite-based applications it emits the
configuration meta tag, the stylesheet links, and the module script tags
all together, while `include_ember_stylesheet_tags` supports only classic
applications. This requires `ember-cli-rails-assets >= 0.9.0`.
* In development, the application is served by Vite's development server —
the same one the application's `npm start` script runs. `ember-cli-rails`
starts it on the first request and shuts it down when Rails exits, and
rewrites the URLs in the `index.html` it serves so that the browser loads
the application's modules, and Vite's HMR client, from it directly. Changes
are hot-reloaded without restarting Rails.

The development server is the default — and the recommended — way to
develop a Vite-based application, and needs no configuration. When
configuring it anyway, a fixed `port` lets several Rails workers (or a
hand-started `npm start`) share a single server, and a longer `timeout`
accommodates a slow first boot:
`ember-cli >= 6.8` generates applications that are built with [Vite] instead of the classic Broccoli-based pipeline.
`ember-cli-rails` supports both build systems, and detects the Vite-based build by the presence of a `vite.config.*` file in the Ember application's root.

When upgrading an Ember application to the Vite-based blueprint, note the following differences in how `ember-cli-rails` treats it:

* Remove `ember-cli-rails-addon` from the application's `package.json`.
The addon is incompatible with the Vite-based build (it forces `storeConfigInMeta` off and ships an initializer that imports the removed `ember` module), and `ember-cli-rails` no longer needs it there.
* Use `include_ember_script_tags` on its own instead of pairing it with `include_ember_stylesheet_tags`: for Vite-based applications it emits the configuration meta tag, the stylesheet links, and the module script tags all together, while `include_ember_stylesheet_tags` supports only classic applications.
This requires `ember-cli-rails-assets >= 0.9.0`.
* In development, the application is served by Vite's development server — the same one the application's `npm start` script runs.
`ember-cli-rails` starts it on the first request and shuts it down when Rails exits, and rewrites the URLs in the `index.html` it serves so that the browser loads the application's modules, and Vite's HMR client, from it directly.
Changes are hot-reloaded without restarting Rails.

The development server is the default — and the recommended — way to develop a Vite-based application, and needs no configuration.
When configuring it anyway, a fixed `port` lets several Rails workers (or a hand-started `npm start`) share a single server, and a longer `timeout` accommodates a slow first boot:

```rb
EmberCli.configure do |c|
c.app :frontend, dev_server: { port: 4200, timeout: 120 }
end
```

When Rails runs in a container and the browser outside of it, bind the
development server to every interface and name the browser-facing origin
separately: `dev_server: { host: "0.0.0.0", port: 4200, origin:
"http://localhost:4200" }`.
Opening the page on a LAN IP or a custom domain also needs `server.cors`
configured in `vite.config.*` — Vite's default allows localhost origins
only.
When Rails runs in a container and the browser outside of it, bind the development server to every interface and name the browser-facing origin separately: `dev_server: { host: "0.0.0.0", port: 4200, origin: "http://localhost:4200" }`.
Opening the page on a LAN IP or a custom domain also needs `server.cors` configured in `vite.config.*` — Vite's default allows localhost origins only.

To opt out of the development server, disable it:

Expand All @@ -53,32 +63,24 @@ following differences in how `ember-cli-rails` treats it:
end
```

* `include_ember_script_tags` serves its startup tags from the development
server, with absolute URLs pointing at it — nothing is built, and changes
are picked up by reloading the page. With `dev_server: false` it reads the
output of `ember build` instead, built once, synchronously, on the first
request; restart the Rails server to pick up changes.
* `include_ember_script_tags` serves its startup tags from the development server, with absolute URLs pointing at it — nothing is built, and changes are picked up by reloading the page.
With `dev_server: false` it reads the output of `ember build` instead, built once, synchronously, on the first request; restart the Rails server to pick up changes.

[Vite]: https://vitejs.dev

# Ruby support

According to [these release notes][latest-eol], Ruby versions prior to `2.5.x`
has been end-of-lifed.
According to [these release notes][latest-eol], Ruby versions prior to `2.5.x` has been end-of-lifed.

Additionally, this codebase makes use of [(required) keyword arguments][kwargs].

From `ember-cli-rails@0.4.0` and on, we will no longer support versions of Ruby
prior to `2.1.0`.
From `ember-cli-rails@0.4.0` and on, we will no longer support versions of Ruby prior to `2.1.0`.

`ember-cli-rails@0.8.0` adds support for Rails 5, which depends on `rack@2.0.x`,
which **requires** Ruby `2.2.2` or greater.
`ember-cli-rails@0.8.0` adds support for Rails 5, which depends on `rack@2.0.x`, which **requires** Ruby `2.2.2` or greater.

From `ember-cli-rails@0.8.0` and on, we will no longer support versions of Ruby
prior to `2.2.2`.
From `ember-cli-rails@0.8.0` and on, we will no longer support versions of Ruby prior to `2.2.2`.

From `ember-cli-rails@0.12.0` and on, we will no longer support versions of Ruby
prior to `2.5.x`.
From `ember-cli-rails@0.12.0` and on, we will no longer support versions of Ruby prior to `2.5.x`.

To use `ember-cli-rails` with older versions of Ruby, try the `0.3.x` series.

Expand All @@ -87,15 +89,12 @@ To use `ember-cli-rails` with older versions of Ruby, try the `0.3.x` series.

# Rails support

According to the [Rails Maintenance Policy][version-policy], Rails versions
prior to `5.2.x` have been end-of-lifed. Additionally, the `4.0.x` series no
longer receives bug fixes of any sort.
According to the [Rails Maintenance Policy][version-policy], Rails versions prior to `5.2.x` have been end-of-lifed.
Additionally, the `4.0.x` series no longer receives bug fixes of any sort.

From `ember-cli-rails@0.4.0` and on, we will no longer support versions of Rails
prior to `3.2.0`, nor will we support the `4.0.x` series of releases.
From `ember-cli-rails@0.4.0` and on, we will no longer support versions of Rails prior to `3.2.0`, nor will we support the `4.0.x` series of releases.

From `ember-cli-rails@0.12.0` and on, we will no longer support versions of
Rails prior to `5.2.0`.
From `ember-cli-rails@0.12.0` and on, we will no longer support versions of Rails prior to `5.2.0`.

To use `ember-cli-rails` with older versions of Rails, try the `0.3.x` series.

Expand Down
2 changes: 1 addition & 1 deletion ember-cli-rails.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Gem::Specification.new do |spec|

spec.required_ruby_version = ">= 2.5.0"

spec.add_dependency "ember-cli-rails-assets", ">= 0.9.0", "< 1.0"
spec.add_dependency "nokogiri", ">= 1.13"
spec.add_dependency "railties", ">= 4.2"
spec.add_dependency "rack", ">= 2.1", "< 4.0"
spec.add_dependency "terrapin", ">= 0.6.0", "< 2.0"
Expand Down
2 changes: 1 addition & 1 deletion lib/ember_cli.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
require "fileutils"
require "active_support/deprecation"
require "ember-cli-rails-assets"
require "ember_cli/engine"
require "ember_cli/configuration"
require "ember_cli/helpers"
require "ember_cli/embedding"
require "ember_cli/errors"

module EmberCli
Expand Down
4 changes: 4 additions & 0 deletions lib/ember_cli/app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ def index_html(head:, body:, mount_point: nil)
html.render
end

def vite?
paths.vite?
end

def install_dependencies
@shell.install
end
Expand Down
49 changes: 49 additions & 0 deletions lib/ember_cli/embedding.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
require "ember_cli/embedding/asset_map"
require "ember_cli/embedding/startup_tags"
require "ember_cli/embedding/url"

module EmberCli
class Embedding
private_constant :AssetMap, :StartupTags, :Url

def initialize(app)
@app = app
end

def startup_tags?
app.dev_server? || app.vite?
end

def startup_tags(prepend: "")
if app.dev_server?
StartupTags.new(app.dev_server.index_html, prefix: app.dev_server.origin).to_a
else
StartupTags.new(index_html.read, prefix: prepend).to_a
end
end

def javascript_assets(prepend: "")
asset_map.javascripts(prepend: prepend)
end

def stylesheet_assets(prepend: "")
asset_map.stylesheets(prepend: prepend)
end

private

attr_reader :app

def asset_map
AssetMap.new(
name: app.name,
index_html: index_html,
assets_path: app.dist_path.join("assets"),
)
end

def index_html
app.dist_path.join("index.html")
end
end
end
92 changes: 92 additions & 0 deletions lib/ember_cli/embedding/asset_map.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
require "nokogiri"

require "ember_cli/errors"
require "ember_cli/embedding/url"

module EmberCli
class Embedding
class AssetMap
PREPEND = "assets/".freeze

def initialize(name:, index_html:, assets_path:)
@name = name
@index_html = index_html
@assets_path = assets_path
end

def javascripts(prepend: "")
assets_referenced_by("script", "src", prepend)
end

def stylesheets(prepend: "")
assets_referenced_by(%{link[rel="stylesheet"]}, "href", prepend)
end

private

attr_reader :name, :index_html, :assets_path

def assets_referenced_by(selector, attribute, prepend)
assert_built!

document.css(selector).filter_map do |tag|
asset_for(tag[attribute], prepend)
end
end

def document
@document ||= Nokogiri::HTML(index_html.read)
end

def asset_for(url, prepend)
if url.to_s.empty?
nil
elsif Url.remote?(url)
url
else
[prepend, asset_matching(url)].join
end
end

# A reference carries directories the build output does not, such as the
# `assets` directory itself or the application's `rootURL`, so match on the
# longest trailing path the two agree on rather than on the file name, which
# an addon's nested asset can share with another file.
def asset_matching(url)
asset = path_suffixes(url).find { |suffix| file_paths.include?(suffix) }

unless asset
fail BuildError, "Failed to find a built asset matching `#{url}`"
end

PREPEND + asset
end

def path_suffixes(url)
segments = url.split("/").reject(&:empty?)

segments.each_index.map { |index| segments[index..].join("/") }
end

def file_paths
@file_paths ||= if assets_path.directory?
assets_path.glob("**/*", File::FNM_DOTMATCH).
select(&:file?).
map { |path| path.relative_path_from(assets_path).to_s }
else
[]
end
end

def assert_built!
if file_paths.empty?
fail BuildError, <<~MSG
Missing built assets for #{name.inspect} in `#{assets_path}`.

Build the application before rendering its assets.
MSG
end
end
end
end
end
45 changes: 45 additions & 0 deletions lib/ember_cli/embedding/startup_tags.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
require "nokogiri"

require "ember_cli/embedding/url"

module EmberCli
class Embedding
class StartupTags
SELECTOR = [
%{meta[name$="/config/environment"]},
%{link[rel="stylesheet"]},
%{link[rel="modulepreload"]},
"script",
].join(", ").freeze

URL_ATTRIBUTES = %w(href src).freeze

def initialize(html, prefix: "")
@html = html
@prefix = prefix.to_s.chomp("/")
end

def to_a
Nokogiri::HTML5(html).css(SELECTOR).map { |tag| prefix_urls(tag).to_html }
end

private

attr_reader :html, :prefix

# Only a root-relative URL points into what this application serves, so a
# URL that resolves elsewhere is left alone.
def prefix_urls(tag)
URL_ATTRIBUTES.each do |attribute|
value = tag[attribute]

if value&.start_with?("/") && !Url.remote?(value)
tag[attribute] = "#{prefix}#{value}"
end
end

tag
end
end
end
end
Loading
Loading