Skip to content
Merged
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: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
Unreleased
------

* Serve the `include_ember_script_tags` startup tags from Vite's development
server when ember-cli-rails serves the application from it, rewriting
root-relative URLs to absolute URLs on the server
* Leave protocol-relative (`//`) URLs alone when remapping startup tags
* Raise the Vite guidance from `include_ember_stylesheet_tags` for
applications served by the development server, whose `dist` may not exist

0.8.1
------

Expand Down
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ gem "rails", rails_constraint
gem "webrick"

group :development, :test do
gem "ember-cli-rails", github: "tricknotes/ember-cli-rails"
gem "ember-cli-rails"
end

group :test do
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ extracted from the generated `index.html`:
<%= include_ember_script_tags :frontend %>
```

When ember-cli-rails serves the application from Vite's development server in
`development`, `include_ember_script_tags` reads `index.html` from the server
instead of a build directory, and emits the startup tags with absolute URLs
pointing at the server. Nothing is built, so changes to the application are
picked up by reloading the page.

`include_ember_stylesheet_tags` only supports classic applications, and raises
an error when called for a Vite-based application.

Expand Down
47 changes: 38 additions & 9 deletions app/helpers/ember_cli_rails_assets_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@ def include_ember_script_tags(name, prepend: "")
app = EmberCli[name]
app.build

paths = EmberCli::Assets::Paths.new(app)

if paths.vite?
vite_ember_script_tags(paths, prepend)
if dev_server?(app)
dev_server_ember_script_tags(app)
else
classic_ember_script_tags(app, prepend)
paths = EmberCli::Assets::Paths.new(app)

if paths.vite?
vite_ember_script_tags(paths, prepend)
else
classic_ember_script_tags(app, prepend)
end
end
end

Expand All @@ -22,7 +26,7 @@ def include_ember_stylesheet_tags(name, prepend: "")

paths = EmberCli::Assets::Paths.new(app)

if paths.vite?
if dev_server?(app) || paths.vite?
raise EmberCli::Assets::NotSupportedError, <<~MSG
`include_ember_stylesheet_tags` does not support Vite-based
applications (`ember-cli >= 6.8`).
Expand All @@ -42,6 +46,22 @@ def include_ember_stylesheet_tags(name, prepend: "")

private

# Whether ember-cli-rails serves the application from Vite's development
# server. Older ember-cli-rails releases have no development server, so
# feature-detect the reader.
def dev_server?(app)
app.respond_to?(:dev_server?) && app.dev_server?
end

# The application is served by Vite's development server, so read
# `index.html` from the server instead of a build directory, and rewrite
# its root-relative URLs to absolute URLs on the server — the same way
# ember-cli-rails serves the document itself. `app.build` has already
# booted the server.
def dev_server_ember_script_tags(app)
vite_startup_tags(app.dev_server.index_html, prefix: app.dev_server.origin)
end

# Classic builds ship a fixed set of scripts (vendor and app) resolved
# through the asset map, so emit a plain script tag per JavaScript asset
# with `prepend` joined onto each path.
Expand All @@ -58,15 +78,24 @@ def classic_ember_script_tags(app, prepend)
# the tags required for startup (including the config meta tag and
# stylesheets) and remap root-absolute paths onto the mount point.
def vite_ember_script_tags(paths, prepend)
document = Nokogiri::HTML5(paths.index_html.read)
prefix = prepend.to_s.chomp("/")
vite_startup_tags(paths.index_html.read, prefix: prepend.to_s.chomp("/"))
end

# Extracts the tags a Vite-based application needs to boot from an
# `index.html` document, with `prefix` joined onto every root-relative
# URL. Protocol-relative URLs (`//`) are left alone.
def vite_startup_tags(html, prefix:)
document = Nokogiri::HTML5(html)

tags = document.css(
'meta[name$="/config/environment"], link[rel="stylesheet"], link[rel="modulepreload"], script'
).map do |tag|
%w(href src).each do |attribute|
value = tag[attribute]
tag[attribute] = "#{prefix}#{value}" if value&.start_with?("/")

if value&.start_with?("/") && !value.start_with?("//")
tag[attribute] = "#{prefix}#{value}"
end
end
tag.to_html.html_safe
end
Expand Down
64 changes: 63 additions & 1 deletion spec/helpers/ember_cli_rails_assets_helper_spec.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,72 @@
require "rails_helper"

describe EmberCliRailsAssetsHelper do
describe "#include_ember_script_tags" do
context "when the application is served by Vite's development server" do
# The development server API ships with the ember-cli-rails release
# that serves Vite-based applications from the development server, so
# plain doubles stand in for `EmberCli::App` and `EmberCli::DevServer`
# here until the Gemfile can be pointed at a release carrying it.
it "emits the startup tags with root-relative URLs rewritten onto the server" do
index_html = <<~HTML
<html>
<head>
<script type="module" src="/@vite/client"></script>
<meta name="my-app/config/environment" content="%7B%7D">
<link rel="stylesheet" href="/@embroider/virtual/app.css">
<script type="module" src="https://cdn.example.com/analytics.js"></script>
<script type="module" src="//cdn.example.com/protocol-relative.js"></script>
</head>
<body>
<script src="/@embroider/virtual/vendor.js"></script>
</body>
</html>
HTML
dev_server = double(
"EmberCli::DevServer",
index_html: index_html,
origin: "http://127.0.0.1:4200",
)
app = double(
"EmberCli::App",
build: true,
dev_server?: true,
dev_server: dev_server,
)
allow(EmberCli).to receive(:[]).with(:frontend).and_return(app)

tags = helper.include_ember_script_tags(:frontend)

expect(app).to have_received(:build)
expect(tags).to include(%{src="http://127.0.0.1:4200/@vite/client"})
expect(tags).to include(%{src="http://127.0.0.1:4200/@embroider/virtual/vendor.js"})
expect(tags).to include(%{href="http://127.0.0.1:4200/@embroider/virtual/app.css"})
expect(tags).to include(%{name="my-app/config/environment"})
expect(tags).to include(%{src="https://cdn.example.com/analytics.js"})
expect(tags).to include(%{src="//cdn.example.com/protocol-relative.js"})
end
end
end

describe "#include_ember_stylesheet_tags" do
context "when the application is served by Vite's development server" do
it "raises an error pointing at `include_ember_script_tags`" do
app = double("EmberCli::App", build: true, dev_server?: true)
paths = instance_double(EmberCli::Assets::Paths)
allow(EmberCli).to receive(:[]).with(:frontend).and_return(app)
allow(EmberCli::Assets::Paths).
to receive(:new).with(app).and_return(paths)

expect { helper.include_ember_stylesheet_tags(:frontend) }.to raise_error(
EmberCli::Assets::NotSupportedError,
/include_ember_script_tags/,
)
end
end

context "when the application is built with Vite" do
it "raises an error pointing at `include_ember_script_tags`" do
app = instance_double(EmberCli::App, build: true)
app = double("EmberCli::App", build: true, dev_server?: false)
paths = instance_double(EmberCli::Assets::Paths, vite?: true)
allow(EmberCli).to receive(:[]).with(:frontend).and_return(app)
allow(EmberCli::Assets::Paths).
Expand Down
Loading