diff --git a/app/views/layouts/alchemy/admin.html.erb b/app/views/layouts/alchemy/admin.html.erb
index 0980bee3d0..7affe444c3 100644
--- a/app/views/layouts/alchemy/admin.html.erb
+++ b/app/views/layouts/alchemy/admin.html.erb
@@ -35,6 +35,13 @@
<%= render 'alchemy/admin/partials/routes' %>
<%= javascript_include_tag('alchemy/admin/all', 'data-turbo-track' => true) %>
<%= javascript_importmap_tags("alchemy_admin", importmap: Alchemy.importmap) %>
+ <% if Alchemy.admin_js_imports.any? %>
+
+ <% end %>
<%= yield :javascript_includes %>
<%= content_tag :body, id: 'alchemy', class: alchemy_body_class do %>
diff --git a/lib/alchemy.rb b/lib/alchemy.rb
index 9e52604c72..b4cc25d568 100644
--- a/lib/alchemy.rb
+++ b/lib/alchemy.rb
@@ -43,6 +43,25 @@ def self.preview_sources=(sources)
@_preview_sources = Array(sources)
end
+ # Additional JS modules to be imported in the Alchemy admin UI
+ #
+ # Be sure to also pin the modules with +Alchemy.importmap+.
+ #
+ # == Example
+ #
+ # Alchemy.importmap.pin "flatpickr/de",
+ # to: "https://ga.jspm.io/npm:flatpickr@4.6.13/dist/l10n/de.js"
+ #
+ # Alchemy.admin_js_imports << "flatpickr/de"
+ #
+ def self.admin_js_imports
+ @_admin_js_imports ||= Set.new
+ end
+
+ def self.admin_js_imports=(sources)
+ @_admin_js_imports = Set[sources]
+ end
+
# Define page publish targets
#
# A publish target is a ActiveJob that gets performed
diff --git a/spec/views/layouts/alchemy/admin.html.erb.spec b/spec/views/layouts/alchemy/admin.html.erb.spec
new file mode 100644
index 0000000000..d2eaa9531a
--- /dev/null
+++ b/spec/views/layouts/alchemy/admin.html.erb.spec
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require "rails_helper"
+
+RSpec.describe "layouts/alchemy/admin.html.erb" do
+ before do
+ view.extend Alchemy::Admin::BaseHelper
+ allow(view).to receive(:alchemy_modules).and_return([])
+ allow(view).to receive(:current_alchemy_user).and_return(DummyUser.new)
+ allow(view).to receive(:configuration).and_return({})
+ end
+
+ subject do
+ render template: "layouts/alchemy/admin"
+ rendered
+ end
+
+ context "with Alchemy.admin_js_imports" do
+ around do |example|
+ current = Alchemy.admin_js_imports
+ Alchemy.admin_js_imports << "foo"
+ example.run
+ Alchemy.admin_js_imports = current
+ end
+
+ it "renders the given javascripts module imports" do
+ expect(subject).to have_selector("script[type=\"module\"]:last-of-type", text: /import "foo"/)
+ end
+ end
+
+ context "without Alchemy.admin_js_imports" do
+ it "does not render the given javascripts module imports" do
+ expect(subject).to_not have_selector("script[type=\"module\"]:last-of-type", text: /import "foo"/)
+ end
+ end
+end