diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3be113cb..12a4db46 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,13 +16,31 @@ jobs: matrix: ruby: ["3.3", "3.4", "4.0"] rails: ["7.2", "8.0", "8.1"] + # `ember` is the `ember-cli/ember-new-output` tag used for the + # dummy app. `>= 6.8` uses the Vite-based blueprint, older + # versions use the classic Broccoli-based build. + ember: ["7.0.0"] include: - - ruby: "4.0" - rails: "main" + # Disabled: rspec-rails 6.x (the newest the gemspec allows) + # fails to load specs against rails/rails main with + # `FrozenError: can't modify frozen Hash`. Re-enable once the + # rspec-rails dependency can be upgraded to a series that + # supports Rails main. + # - ruby: "4.0" + # rails: "main" + # ember: "7.0.0" + # Classic (Broccoli-based) blueprint coverage + - ruby: "3.4" + rails: "8.1" + ember: "5.12.0" + - ruby: "3.4" + rails: "8.1" + ember: "6.7.2" env: RAILS_ENV: "test" RAILS_VERSION: "${{ matrix.rails }}" + EMBER_VERSION: "${{ matrix.ember }}" steps: - uses: "actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1" # v7.0.1 @@ -30,7 +48,7 @@ jobs: - name: "Install NodeJS" uses: "actions/setup-node@820762786026740c76f36085b0efc47a31fe5020" # v7.0.0 with: - node-version: "20.x" + node-version: "22.x" - name: "Install Ruby ${{ matrix.ruby }}" uses: "ruby/setup-ruby@003a5c4d8d6321bd302e38f6f0ec593f77f06600" # v1.319.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index dfa933b6..d46f30da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ main ------ +* Support Ember applications generated with the Vite-based blueprint + (`ember-cli >= 6.8`), without `ember-cli-rails-addon` +* Require `ember-cli-rails-assets >= 0.8.0`, which adds Vite support to + `include_ember_script_tags` + 0.12.3 ------ diff --git a/README.md b/README.md index 3fe7dcb0..17a8e5ff 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,8 @@ EmberCli.configure do |c| end ``` -Next, install the [ember-cli-rails-addon][addon]: +Next, if your Ember application uses the classic (Broccoli-based) build +(generated with `ember-cli < 6.8`), install the [ember-cli-rails-addon][addon]: ```bash $ cd path/to/frontend @@ -111,6 +112,10 @@ For instance, if you're using the `0.6.x` version of the gem, specify } ``` +If your Ember application uses the Vite-based build (generated with +`ember-cli >= 6.8`), do **not** install the addon: it is incompatible with the +Vite-based build, and `ember-cli-rails` does not require it there. + [addon]: https://github.com/rondale-sc/ember-cli-rails-addon/ [semver]: http://semver.org/ @@ -171,6 +176,17 @@ suites, configure the `default` task to depend on both `spec` and `ember:test`. task default: [:spec, "ember:test"] ``` +**Vite-based applications** + +When Rails is running in development mode, classic (Broccoli-based) Ember +applications are built with `ember build --watch`, so changes to the Ember +application are picked up automatically. + +Vite-based Ember applications (generated with `ember-cli >= 6.8`) are instead +built once, synchronously, when they are first requested. To pick up changes +to the Ember application, restart the Rails server, or iterate on the Ember +application directly with its own development server (`npm start`). + ## Deploy When Rails is running in production mode, EmberCLI-Rails stops doing runtime @@ -449,6 +465,13 @@ 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. +**Note:** for Vite-based applications (generated with `ember-cli >= 6.8`), +use `include_ember_script_tags` on its own. It emits everything the +application needs to boot — the configuration `` tag, the stylesheet +and `modulepreload` links, and the ES module script tags — extracted from +the generated `index.html`. `include_ember_stylesheet_tags` only supports +classic (Broccoli-based) applications. + Following the example above, configure the mounted EmberCLI application to be served by a custom controller (`ApplicationController`, in this case). @@ -654,7 +677,20 @@ if (environment === 'development') { This project supports: -* EmberCLI versions `>= 1.13.13` +* EmberCLI versions `>= 1.13.13` using the classic (Broccoli-based) build, + together with [ember-cli-rails-addon][addon] +* EmberCLI versions `>= 6.8` using the Vite-based build, without the addon + +The test suite currently exercises the `5.12.x` and `6.7.x` (classic) and +`7.0.x` (Vite) series. + +Note the following limitations for Vite-based applications: + +* `include_ember_script_tags` emits the full set of tags the application + needs to boot, stylesheets included; `include_ember_stylesheet_tags` is + classic-only and must not be called for Vite-based applications +* in development, the application is built synchronously on first request + instead of being rebuilt on file changes ## Ruby and Rails support diff --git a/UPGRADING.md b/UPGRADING.md index c845bfd1..2a4ac69a 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,3 +1,29 @@ +# 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.8.0`. +* In development, the application is built synchronously on first request + instead of being rebuilt on file changes. Restart the Rails server to pick + up changes, or iterate with the Ember application's own development server + (`npm start`). + +[Vite]: https://vitejs.dev + # Ruby support According to [these release notes][latest-eol], Ruby versions prior to `2.5.x` diff --git a/bin/setup b/bin/setup index 4bf4f2f9..3b77ff20 100755 --- a/bin/setup +++ b/bin/setup @@ -9,10 +9,6 @@ bundle check || bundle install # Add binstubs to PATH via export PATH=".git/safe/../../bin:$PATH" in ~/.zshenv mkdir -p .git/safe -if ! command -v bower > /dev/null; then - npm install -g bower -fi - bin/setup_ember spec/dummy/my-app echo '-- Install Ember dependencies' diff --git a/bin/setup_ember b/bin/setup_ember index e3dc140c..0b275c0c 100755 --- a/bin/setup_ember +++ b/bin/setup_ember @@ -2,28 +2,47 @@ set -e +# EMBER_VERSION selects the `ember-cli/ember-new-output` tag to clone. +# Versions >= 6.8 use the Vite-based app blueprint, older versions use the +# classic Broccoli-based build. +EMBER_VERSION="${EMBER_VERSION:-7.0.0}" + setup_ember() { local target="${1-spec/dummy/my-app}" if ! [ -d $target ]; then - git clone -b 'v4.0.0' https://github.com/ember-cli/ember-new-output.git $target + git clone -b "v${EMBER_VERSION}" https://github.com/ember-cli/ember-new-output.git $target - echo '-- Make router catchall routes' - sed -i -e "s/'auto'/'hash'/" $target/config/environment.js + echo '-- Use hash location to serve the app from any mount point' + sed -i -e "s/locationType: '\(auto\|history\)'/locationType: 'hash'/" $target/config/environment.js echo '-- Add an image to a template' + if [ -f "$target/app/templates/application.gjs" ]; then + # The Vite blueprint ships an `application.gjs`, which would conflict + # with the `application.hbs` fixture. + rm "$target/app/templates/application.gjs" + fi cp spec/fixtures/application.hbs $target/app/templates/application.hbs mkdir -p $target/public/assets cp spec/fixtures/logo.png $target/public/assets - echo '-- Install ember-cli-rails-addon' - cd $target && - npm install --save-dev ember-cli-rails-addon@rondale-sc/ember-cli-rails-addon + if [ -f "$target/vite.config.mjs" ]; then + # `ember-cli-rails-addon` is incompatible with the Vite-based + # blueprint, and `ember-cli-rails` does not require it there. + echo '-- Install NPM dependencies' + cd $target && + npm install + else + echo '-- Install ember-cli-rails-addon' + cd $target && + npm install --save-dev ember-cli-rails-addon@rondale-sc/ember-cli-rails-addon + fi - if [ -f "$target/bower.json" ]; then + if [ -f "bower.json" ]; then echo '-- Install Bower dependencies' - cd $target && bower install + command -v bower > /dev/null || npm install -g bower + bower install fi echo '-- Successfully setup Ember' diff --git a/ember-cli-rails.gemspec b/ember-cli-rails.gemspec index 7c0a0442..8558f3f6 100644 --- a/ember-cli-rails.gemspec +++ b/ember-cli-rails.gemspec @@ -15,7 +15,7 @@ Gem::Specification.new do |spec| spec.required_ruby_version = ">= 2.5.0" - spec.add_dependency "ember-cli-rails-assets", ">= 0.6.2", "< 1.0" + spec.add_dependency "ember-cli-rails-assets", ">= 0.8.0", "< 1.0" spec.add_dependency "railties", ">= 4.2" spec.add_dependency "rack", ">= 2.1", "< 4.0" spec.add_dependency "terrapin", "~> 0.6.0" diff --git a/lib/ember_cli/app.rb b/lib/ember_cli/app.rb index ac1f28ed..1ac42137 100644 --- a/lib/ember_cli/app.rb +++ b/lib/ember_cli/app.rb @@ -50,7 +50,14 @@ def compile def build unless EmberCli.skip? if development? - build_and_watch + if paths.vite? + # The Vite-based blueprint (`ember-cli >= 6.8`) has no + # `ember-cli-rails-addon` to manage the build lock, so build + # synchronously instead of watching for changes. + compile + else + build_and_watch + end elsif test? compile end diff --git a/lib/ember_cli/path_set.rb b/lib/ember_cli/path_set.rb index 9a30e2c8..41d0c22e 100644 --- a/lib/ember_cli/path_set.rb +++ b/lib/ember_cli/path_set.rb @@ -39,6 +39,13 @@ def bower_json root.join("bower.json") end + # Apps generated with the Vite-based blueprint (`ember-cli >= 6.8`) + # ship a Vite config file at their root. + def vite? + %w[vite.config.mjs vite.config.js vite.config.ts]. + any? { |config| root.join(config).exist? } + end + def ember @ember ||= begin root.join("node_modules", "ember-cli", "bin", "ember").tap do |path| diff --git a/spec/dummy/app/views/pages/embedded.html.erb b/spec/dummy/app/views/pages/embedded.html.erb index 8d07285c..93cf0d74 100644 --- a/spec/dummy/app/views/pages/embedded.html.erb +++ b/spec/dummy/app/views/pages/embedded.html.erb @@ -1,2 +1,6 @@ <%= include_ember_script_tags "my-app" %> -<%= include_ember_stylesheet_tags "my-app" %> +<%# For Vite-based apps, `include_ember_script_tags` already emits the + stylesheet tags, and `include_ember_stylesheet_tags` is classic-only. %> +<% unless EmberCli["my-app"].paths.vite? %> + <%= include_ember_stylesheet_tags "my-app" %> +<% end %> diff --git a/spec/features/user_views_ember_app_spec.rb b/spec/features/user_views_ember_app_spec.rb index 71fcddb2..72e2d520 100644 --- a/spec/features/user_views_ember_app_spec.rb +++ b/spec/features/user_views_ember_app_spec.rb @@ -36,27 +36,27 @@ end scenario "is redirected with trailing slash", js: false do - expect(embedded_path).to eq("/asset-helpers") + expect(include_index_path).to eq("/no-block") - visit embedded_path + visit include_index_path - expect(current_path).to eq("/asset-helpers/") + expect(current_path).to eq("/no-block/") end scenario "is redirected with trailing slash with query params", js: false do - expect(embedded_path(query: "foo")).to eq("/asset-helpers?query=foo") + expect(include_index_path(query: "foo")).to eq("/no-block?query=foo") - visit embedded_path(query: "foo") + visit include_index_path(query: "foo") - expect(page).to have_current_path("/asset-helpers/?query=foo") + expect(page).to have_current_path("/no-block/?query=foo") end scenario "is not redirected with trailing slash with params", js: false do - expect(embedded_path(query: "foo")).to eq("/asset-helpers?query=foo") + expect(include_index_path(query: "foo")).to eq("/no-block?query=foo") - visit "/asset-helpers/?query=foo" + visit "/no-block/?query=foo" - expect(page).to have_current_path("/asset-helpers/?query=foo") + expect(page).to have_current_path("/no-block/?query=foo") end def have_client_side_asset diff --git a/spec/lib/ember_cli/path_set_spec.rb b/spec/lib/ember_cli/path_set_spec.rb index 5582b7ea..2ca3eeaa 100644 --- a/spec/lib/ember_cli/path_set_spec.rb +++ b/spec/lib/ember_cli/path_set_spec.rb @@ -155,6 +155,30 @@ end end + describe "#vite?" do + it "is false when the app has no Vite config" do + path_set = build_path_set + + expect(path_set).not_to be_vite + end + + it "is true when the app has a vite.config.mjs" do + app = build_app + create_file(app_root_for(app).join("vite.config.mjs")) + path_set = build_path_set(app: app) + + expect(path_set).to be_vite + end + + it "is true when the app has a vite.config.js" do + app = build_app + create_file(app_root_for(app).join("vite.config.js")) + path_set = build_path_set(app: app) + + expect(path_set).to be_vite + end + end + describe "#bower_components" do it "is a child of #root" do app = build_app(name: "foo") diff --git a/spec/requests/assets/my-app.js_spec.rb b/spec/requests/assets/cache_control_spec.rb similarity index 58% rename from spec/requests/assets/my-app.js_spec.rb rename to spec/requests/assets/cache_control_spec.rb index dc9656fa..912b25e1 100644 --- a/spec/requests/assets/my-app.js_spec.rb +++ b/spec/requests/assets/cache_control_spec.rb @@ -1,8 +1,8 @@ -describe "GET assets/my-app.js" do +describe "GET a JavaScript asset" do it "responds with the 'Cache-Control' header from Rails" do build_ember_cli_assets - get "/assets/my-app.js" + get "/assets/#{javascript_asset_name}" expect(headers["Cache-Control"]).to eq(cache_for_five_minutes) end @@ -11,6 +11,12 @@ def build_ember_cli_assets EmberCli["my-app"].build end + def javascript_asset_name + assets = EmberCli["my-app"].dist_path.join("assets") + + Pathname.glob(assets.join("*.js")).first.basename + end + def cache_for_five_minutes Dummy::Application::CACHE_CONTROL_FIVE_MINUTES end diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb index 039bf78d..fc9746ef 100644 --- a/spec/support/capybara.rb +++ b/spec/support/capybara.rb @@ -1,12 +1,20 @@ require "selenium/webdriver" Capybara.register_driver :headless_chrome do |app| + options = Selenium::WebDriver::Chrome::Options.new( + args: %w[--no-sandbox --headless], + ) + + # Point Selenium at a specific Chrome binary, e.g. inside a container + # without a system-wide Chrome installation. + if ENV["CHROME_BIN"] + options.binary = ENV["CHROME_BIN"] + end + Capybara::Selenium::Driver.new( app, browser: :chrome, - options: Selenium::WebDriver::Chrome::Options.new( - args: %w[--no-sandbox --headless], - ), + options: options, ) end