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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ main
* Raise `EmberCli::BuildError` when `ember build` or a dependency
installation fails, instead of exiting the process from inside the gem —
`rake ember:compile` and `rake ember:install` still exit nonzero
* Remap the root-relative asset URLs in a Vite build's `index.html` onto the
mount point when `mount_ember_app` serves the application from a path other
than `/`, so non-root mounts work without a matching `rootURL`

0.13.1
------
Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,15 @@ Rails.application.routes.draw do
end
```

Then set each Ember application's `rootURL` to the mount point:
The `index.html` of a Vite build refers to its assets with root-relative URLs.
When the application is served from the build output, `mount_ember_app` remaps
those references onto the mount point, where the assets are served — references
that already carry the mount point, from a `rootURL` configured to match it,
are left alone. Development-server-backed applications are unaffected: their
asset URLs point at the development server absolutely.

For classic (Broccoli-based) applications, set each Ember application's
`rootURL` to the mount point:

```javascript
// frontend/config/environment.js
Expand Down
8 changes: 7 additions & 1 deletion app/helpers/ember_rails_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ def render_ember_app(name, &block)

head, body = markup_capturer.capture

render html: EmberCli[name].index_html(head: head, body: body).html_safe
index_html = EmberCli[name].index_html(
head: head,
body: body,
mount_point: params[:ember_mount_point],
)

render html: index_html.html_safe
end
end
23 changes: 21 additions & 2 deletions lib/ember_cli/app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,11 @@ def build
end
end

def index_html(head:, body:)
def index_html(head:, body:, mount_point: nil)
html = HtmlPage::Renderer.new(
head: head,
body: body,
content: deploy.index_html,
content: remap_to_mount_point(deploy.index_html, mount_point),
)

html.render
Expand Down Expand Up @@ -131,6 +131,25 @@ def test?
env.to_s == "test"
end

# The `index.html` of a Vite build refers to its assets with root-relative
# URLs. When the application is mounted somewhere other than `/`, remap
# them onto the mount point, where `mount_ember_assets` serves them.
# References that already carry the mount point — a `rootURL` configured
# to match it — are left alone.
def remap_to_mount_point(html, mount_point)
prefix = mount_point.to_s.chomp("/")

if prefix.empty? || !paths.vite?
return html
end

already_mounted = Regexp.escape(prefix.delete_prefix("/"))

html.gsub(%r{(\s)(src|href)=(["'])/(?!/)(?!#{already_mounted}/)}i) do
"#{$1}#{$2}=#{$3}#{prefix}/"
end
end

def deploy
deploy_strategy.new(self)
end
Expand Down
2 changes: 1 addition & 1 deletion lib/ember_cli/route_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module Routing
class Mapper
def mount_ember_app(app_name, to:, **options)
routing_options = options.deep_merge(
defaults: { ember_app: app_name },
defaults: { ember_app: app_name, ember_mount_point: to },
)

routing_options.reverse_merge!(
Expand Down
11 changes: 11 additions & 0 deletions spec/features/user_views_ember_app_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@
end
end

scenario "remaps the document's asset URLs onto the mount point", js: false do
unless EmberCli["my-app"].paths.vite?
skip "Root-relative asset URLs are only remapped for Vite builds"
end

visit include_index_path

expect(page).to have_css(%{script[src^="/no-block/"]}, visible: false)
expect(page).to have_no_css(%{script[src^="/@embroider"]}, visible: false)
end

scenario "is redirected with trailing slash", js: false do
expect(include_index_path).to eq("/no-block")

Expand Down
48 changes: 48 additions & 0 deletions spec/lib/ember_cli/app_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,50 @@
end
end

describe "#index_html" do
it "remaps a Vite build's root-relative URLs onto the mount point" do
app = build_app("frontend", vite: true, environment: "test")
stub_deployed_index_html(app, <<~HTML)
<html><head>
<script type="module" src="/@embroider/virtual/vendor.js"></script>
<link rel="stylesheet" href="/assets/app.css">
<script type="module" src="https://cdn.example.com/analytics.js"></script>
<script src="//cdn.example.com/protocol-relative.js"></script>
<link rel="stylesheet" href="/admin/already-mounted.css">
</head><body></body></html>
HTML

index_html = app.index_html(head: "", body: "", mount_point: "/admin")

expect(index_html).to include(%{src="/admin/@embroider/virtual/vendor.js"})
expect(index_html).to include(%{href="/admin/assets/app.css"})
expect(index_html).to include(%{src="https://cdn.example.com/analytics.js"})
expect(index_html).to include(%{src="//cdn.example.com/protocol-relative.js"})
expect(index_html).to include(%{href="/admin/already-mounted.css"})
expect(index_html).not_to include(%{/admin/admin/})
end

it "leaves the document alone when mounted at the root" do
app = build_app("frontend", vite: true, environment: "test")
content = %{<html><head><script src="/assets/app.js"></script></head><body></body></html>}
stub_deployed_index_html(app, content)

index_html = app.index_html(head: "", body: "", mount_point: "/")

expect(index_html).to include(%{src="/assets/app.js"})
end

it "leaves a classic build alone" do
app = build_app("frontend", vite: false, environment: "test")
content = %{<html><head><script src="/assets/app.js"></script></head><body></body></html>}
stub_deployed_index_html(app, content)

index_html = app.index_html(head: "", body: "", mount_point: "/admin")

expect(index_html).to include(%{src="/assets/app.js"})
end
end

describe "#root_path" do
it "delegates to PathSet" do
root_path = Pathname.new(".")
Expand Down Expand Up @@ -197,6 +241,10 @@ def build_app(name, vite:, environment:, **options)
EmberCli::App.new(name, **options)
end

def stub_deployed_index_html(app, html)
allow(app).to receive(:deploy).and_return(double(index_html: html))
end

def stub_paths(method_to_value)
allow_any_instance_of(EmberCli::PathSet).
to receive(method_to_value.keys.first).
Expand Down
Loading