DEV: Allow us to use Ember CLI assets in production

DEV: Allow us to use Ember CLI assets in production

This adds an optional ENV variable, EMBER_CLI_PROD_ASSETS. If truthy, compiling production assets will be done via Ember CLI and will replace the assets Rails would otherwise use.

diff --git a/app/assets/javascripts/discourse/app/index.html b/app/assets/javascripts/discourse/app/index.html
index 1fedae9..88ac513 100644
--- a/app/assets/javascripts/discourse/app/index.html
+++ b/app/assets/javascripts/discourse/app/index.html
@@ -28,8 +28,7 @@
     <bootstrap-content key="hidden-login-form">
     <bootstrap-content key="preloaded">
 
-    <script src="{{rootURL}}assets/scripts/start-app.js"></script>
-    <script src="{{rootURL}}assets/scripts/discourse-boot.js"></script>
+    <script src="{{rootURL}}assets/start-discourse.js"></script>
 
     <bootstrap-content key="body-footer">
     {{content-for "body-footer"}}
diff --git a/app/assets/javascripts/discourse/ember-cli-build.js b/app/assets/javascripts/discourse/ember-cli-build.js
index 29e9a39..4221a95 100644
--- a/app/assets/javascripts/discourse/ember-cli-build.js
+++ b/app/assets/javascripts/discourse/ember-cli-build.js
@@ -19,6 +19,12 @@ module.exports = function (defaults) {
     "ember-qunit": {
       insertContentForTestBody: false,
     },
+    sourcemaps: {
+      // There seems to be a bug with brocolli-concat when sourcemaps are disabled
+      // that causes the `app.import` statements below to fail in production mode.
+      // This forces the use of `fast-sourcemap-concat` which works in production.
+      enabled: true,
+    },
   });
 
   // Ember CLI does this by default for the app tree, but for our extra bundles we
@@ -60,5 +66,12 @@ module.exports = function (defaults) {
       })
     ),
     digest(prettyTextEngine(vendorJs, "discourse-markdown")),
+    digest(
+      concat("public/assets/scripts", {
+        outputFile: `assets/start-discourse.js`,
+        headerFiles: [`start-app.js`],
+        inputFiles: [`discourse-boot.js`],
+      })
+    ),
   ]);
 };
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 9dbc7ae..56996a4 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -13,6 +13,28 @@ module ApplicationHelper
     @extra_body_classes ||= Set.new
   end
 
+  def discourse_config_environment
+    # TODO: Can this come from Ember CLI somehow?
+    { modulePrefix: "discourse",
+      environment: Rails.env,
+      rootURL: Discourse.base_path,
+      locationType: "auto",
+      historySupportMiddleware: false,
+      EmberENV: {
+        FEATURES: {},
+        EXTEND_PROTOTYPES: { "Date": false },
+        _APPLICATION_TEMPLATE_WRAPPER: false,
+        _DEFAULT_ASYNC_OBSERVERS: true,
+        _JQUERY_INTEGRATION: true
+      },
+      APP: {
+        name: "discourse",
+        version: "#{Discourse::VERSION::STRING} #{Discourse.git_version}",
+        exportApplicationGlobal: true
+      }
+    }.to_json
+  end
+
   def google_universal_analytics_json(ua_domain_name = nil)
     result = {}
     if ua_domain_name
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index b5ec502..3446d78 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -59,6 +59,7 @@
 
     <%= tag.meta id: 'data-discourse-setup', data: client_side_setup_data %>
 
+    <meta name="discourse/config/environment" content="<%=u discourse_config_environment %>" />
     <%- if authentication_data %>
       <meta id="data-authentication" data-authentication-data="<%= authentication_data %>">
     <%- end %>
diff --git a/config/application.rb b/config/application.rb
index b1a4d82..89f709c 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -197,9 +197,11 @@ module Discourse
       app.config.assets.precompile += ['application.js']
 
       start_path = ::Rails.root.join("app/assets").to_s
-      exclude = ['.es6', '.hbs', '.hbr', '.js', '.css', '']
+      exclude = ['.es6', '.hbs', '.hbr', '.js', '.css', '.lock', '.json', '.log', '.html', '']
       app.config.assets.precompile << lambda do |logical_path, filename|
         filename.start_with?(start_path) &&
+        !filename.include?("/node_modules/") &&
+        !filename.include?("/dist/") &&
         !exclude.include?(File.extname(logical_path))
       end
     end
diff --git a/lib/tasks/assets.rake b/lib/tasks/assets.rake
index 69f8b3f..d6b6c34 100644
--- a/lib/tasks/assets.rake
+++ b/lib/tasks/assets.rake
@@ -18,9 +18,7 @@ task 'assets:precompile:before' do
   # is recompiled
   Emoji.clear_cache
 
-  if !`which terser`.empty? && !ENV['SKIP_NODE_UGLIFY']
-    $node_uglify = true
-  end
+  $node_compress = `which terser`.present? && !ENV['SKIP_NODE_UGLIFY']
 
   unless ENV['USE_SPROCKETS_UGLIFY']
     $bypass_sprockets_uglify = true
@@ -36,6 +34,15 @@ task 'assets:precompile:before' do
 
   require 'sprockets'
   require 'digest/sha1'
+
+  if ENV['EMBER_CLI_PROD_ASSETS']
+    # Remove the assets that Ember CLI will handle for us
+    Rails.configuration.assets.precompile.reject! do |asset|
+      asset.is_a?(String) &&
+        (%w(application.js admin.js ember_jquery.js pretty-text-bundle.js start-discourse.js vendor.js).include?(asset) ||
+          asset.start_with?("discourse/tests"))
+    end
+  end
 end
 
 task 'assets:precompile:css' => 'environment' do
@@ -165,7 +172,7 @@ def max_compress?(path, locales)
 end
 
 def compress(from, to)
-  if $node_uglify
+  if $node_compress
     compress_node(from, to)
   else
     compress_ruby(from, to)
@@ -216,8 +223,66 @@ def copy_maxmind(from_path, to_path)
   end
 end
 
+def copy_ember_cli_assets
+  ember_dir = "app/assets/javascripts/discourse"
+  ember_cli_assets = "#{ember_dir}/dist/assets/"
+  assets = {}
+  files = {}
+
+  system("yarn --cwd #{ember_dir} run ember build -prod")
+
+  # Copy assets and generate manifest data
+  Dir["#{ember_cli_assets}**/*"].each do |f|
+    if f !~ /test/ && File.file?(f)
+      rel_file = f.sub(ember_cli_assets, "")
+      digest = f.scan(/\-([a-f0-9]+)\./)[0][0]
+
+      dest = "public/assets"
+      dest_sub = dest
+      if rel_file =~ /^([a-z\-\_]+)\//
+        dest_sub = "#{dest}/#{Regexp.last_match[1]}"
+      end
+
+      FileUtils.mkdir_p(dest_sub) unless Dir.exists?(dest_sub)
+      log_file = File.basename(rel_file).sub("-#{digest}", "")
+
+      # It's simpler to serve the file as `application.js`
+      if log_file == "discourse.js"
+        log_file = "application.js"
+        rel_file.sub!(/^discourse/, "application")
+      end
+
+      res = FileUtils.cp(f, "#{dest}/#{rel_file}")
+
+      assets[log_file] = rel_file
+      files[rel_file] = {
+        "logical_path" => log_file,
+        "mtime" => File.mtime(f).iso8601(9),
+        "size" => File.size(f),
+        "digest" => digest,
+        "integrity" => "sha384-#{Base64.encode64(Digest::SHA384.digest(File.read(f))).chomp}"
+      }
+    end
+  end
+
+  # Update manifest file
+  manifest_result = Dir["public/assets/.sprockets-manifest-*.json"]
+  if manifest_result && manifest_result.size == 1
+    json = JSON.parse(File.read(manifest_result[0]))
+    json['files'].merge!(files)
+    json['assets'].merge!(assets)
+    File.write(manifest_result[0], json.to_json)
+  end
+end
+
+task 'test_ember_cli_copy' do
+  copy_ember_cli_assets
+end
+
 task 'assets:precompile' => 'assets:precompile:before' do
 
+  copy_ember_cli_assets if ENV['EMBER_CLI_PROD_ASSETS']
+
   refresh_days = GlobalSetting.refresh_maxmind_db_during_precompile_days
 
   if refresh_days.to_i > 0
@@ -261,6 +326,7 @@ task 'assets:precompile' => 'assets:precompile:before' do
     puts "Compressing Javascript and Generating Source Maps"
     startAll = Process.clock_gettime(Process::CLOCK_MONOTONIC)
     manifest = Sprockets::Manifest.new(assets_path)
+
     locales = Set.new(["en"])
 
     RailsMultisite::ConnectionManagement.each_connection do |db|

GitHub sha: 18c5e9338f723b8017dd0ee14b7ffdd565ec5b2b

This commit appears in #13906 which was approved by CvX. It was merged by eviltrout.