diff --git a/core/.eslintrc.json b/core/.eslintrc.json
index 218d84f937..81ed0cccb5 100644
--- a/core/.eslintrc.json
+++ b/core/.eslintrc.json
@@ -12,6 +12,7 @@
     "domready": true,
     "jQuery": true,
     "_": true,
+    "loadjs": true,
     "matchMedia": true,
     "Backbone": true,
     "Modernizr": true,
diff --git a/core/assets/vendor/loadjs/loadjs.min.js b/core/assets/vendor/loadjs/loadjs.min.js
new file mode 100644
index 0000000000..24f62cadc9
--- /dev/null
+++ b/core/assets/vendor/loadjs/loadjs.min.js
@@ -0,0 +1 @@
+loadjs=function(){function e(e,n){e=e.push?e:[e];var t,r,i,c,o=[],f=e.length,a=f;for(t=function(e,t){t.length&&o.push(e),--a||n(o)};f--;)r=e[f],i=s[r],i?t(r,i):(c=u[r]=u[r]||[],c.push(t))}function n(e,n){if(e){var t=u[e];if(s[e]=n,t)for(;t.length;)t[0](e,n),t.splice(0,1)}}function t(e,n,r,i){var o,s,u=document,f=r.async,a=(r.numRetries||0)+1,h=r.before||c;i=i||0,/(^css!|\.css$)/.test(e)?(o=!0,s=u.createElement("link"),s.rel="stylesheet",s.href=e.replace(/^css!/,"")):(s=u.createElement("script"),s.src=e,s.async=void 0===f||f),s.onload=s.onerror=s.onbeforeload=function(c){var u=c.type[0];if(o&&"hideFocus"in s)try{s.sheet.cssText.length||(u="e")}catch(e){u="e"}if("e"==u&&(i+=1)<a)return t(e,n,r,i);n(e,u,c.defaultPrevented)},h(e,s)!==!1&&u.head.appendChild(s)}function r(e,n,r){e=e.push?e:[e];var i,c,o=e.length,s=o,u=[];for(i=function(e,t,r){if("e"==t&&u.push(e),"b"==t){if(!r)return;u.push(e)}--o||n(u)},c=0;c<s;c++)t(e[c],i,r)}function i(e,t,i){var s,u;if(t&&t.trim&&(s=t),u=(s?i:t)||{},s){if(s in o)throw"LoadJS";o[s]=!0}r(e,function(e){e.length?(u.error||c)(e):(u.success||c)(),n(s,e)},u)}var c=function(){},o={},s={},u={};return i.ready=function(n,t){return e(n,function(e){e.length?(t.error||c)(e):(t.success||c)()}),i},i.done=function(e){n(e,[])},i.reset=function(){o={},s={},u={}},i.isDefined=function(e){return e in o},i}();
\ No newline at end of file
diff --git a/core/core.libraries.yml b/core/core.libraries.yml
index 5da51e8ae5..eaa50970bb 100644
--- a/core/core.libraries.yml
+++ b/core/core.libraries.yml
@@ -95,6 +95,7 @@ drupal.ajax:
     - core/drupalSettings
     - core/drupal.progress
     - core/jquery.once
+    - core/loadjs
 
 drupal.announce:
   version: VERSION
@@ -803,6 +804,16 @@ jquery.ui.widget:
   dependencies:
     - core/jquery.ui
 
+loadjs:
+  remote: https://github.com/muicss/loadjs
+  version: 3.5.0
+  license:
+    name: MIT
+    url: https://github.com/muicss/loadjs/blob/master/LICENSE.txt
+    gpl-compatible: true
+  js:
+    assets/vendor/loadjs/loadjs.min.js: { minified: true }
+
 matchmedia:
   remote: https://github.com/paulirish/matchMedia.js
   version: &matchmedia_version 0.2.0
diff --git a/core/lib/Drupal/Core/Ajax/AddJsCommand.php b/core/lib/Drupal/Core/Ajax/AddJsCommand.php
new file mode 100644
index 0000000000..e44fcf38cb
--- /dev/null
+++ b/core/lib/Drupal/Core/Ajax/AddJsCommand.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace Drupal\Core\Ajax;
+
+/**
+ * An AJAX command for adding JS to the page via ajax.
+ *
+ * This command is implemented by Drupal.AjaxCommands.prototype.add_js()
+ * defined in misc/ajax.js.
+ *
+ * @see misc/ajax.js
+ *
+ * @ingroup ajax
+ */
+class AddJsCommand implements CommandInterface {
+  /**
+   * A CSS selector string.
+   *
+   * If the command is a response to a request from an #ajax form element then
+   * this value can be NULL.
+   *
+   * @var string
+   */
+  protected $selector;
+
+  /**
+   * An array containing the attributes of the scripts to be added to the page.
+   *
+   * @var string[]
+   */
+  protected $styles;
+
+  /**
+   * Constructs an AddJsCommand.
+   *
+   * @param string $selector
+   *   A CSS selector.
+   * @param array $scripts
+   *   An array containing the attributes of the scripts to be added to the page.
+   */
+  public function __construct($selector, $scripts) {
+    $this->selector = $selector;
+    $this->styles = $scripts;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function render() {
+
+    return [
+      'command' => 'add_js',
+      'selector' => $this->selector,
+      'data' => $this->styles,
+    ];
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php b/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php
index ee5208b078..724a1bfa9b 100644
--- a/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php
+++ b/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php
@@ -174,11 +174,13 @@ protected function buildAttachmentsCommands(AjaxResponse $response, Request $req
     }
     if ($js_assets_header) {
       $js_header_render_array = $this->jsCollectionRenderer->render($js_assets_header);
-      $resource_commands[] = new PrependCommand('head', $this->renderer->renderPlain($js_header_render_array));
+      $scripts_attributes = array_map(function ($render_array) { return $render_array['#attributes']; }, $js_header_render_array);
+      $resource_commands[] = new AddJsCommand('head', $scripts_attributes);
     }
     if ($js_assets_footer) {
       $js_footer_render_array = $this->jsCollectionRenderer->render($js_assets_footer);
-      $resource_commands[] = new AppendCommand('body', $this->renderer->renderPlain($js_footer_render_array));
+      $scripts_attributes = array_map(function ($render_array) { return $render_array['#attributes']; }, $js_footer_render_array);
+      $resource_commands[] = new AddJsCommand('body', $scripts_attributes);
     }
     foreach (array_reverse($resource_commands) as $resource_command) {
       $response->addCommand($resource_command, TRUE);
diff --git a/core/misc/ajax.js b/core/misc/ajax.js
index fefe9f3031..08fccb38fa 100644
--- a/core/misc/ajax.js
+++ b/core/misc/ajax.js
@@ -11,7 +11,7 @@
  * included to provide Ajax capabilities.
  */
 
-(function ($, window, Drupal, drupalSettings) {
+(function ($, window, Drupal, drupalSettings, loadjs) {
 
   'use strict';
 
@@ -859,14 +859,26 @@
     // Track if any command is altering the focus so we can avoid changing the
     // focus set by the Ajax command.
     var focusChanged = false;
-    for (var i in response) {
-      if (response.hasOwnProperty(i) && response[i].command && this.commands[response[i].command]) {
-        this.commands[response[i].command](this, response[i], status);
-        if (response[i].command === 'invoke' && response[i].method === 'focus') {
-          focusChanged = true;
+
+    var that = this;
+    Object.keys(response).reduce(function (deferredCommand, i) {
+      return deferredCommand.then(function () {
+        var command = response[i].command;
+
+        if (command && that.commands[command]) {
+          if (command === 'invoke' && response[i].method === 'focus') {
+            focusChanged = true;
+          }
+
+          if (command === 'add_js') {
+            return that.commands[command](that, response[i], status);
+          }
+          else {
+            that.commands[command](that, response[i], status);
+          }
         }
-      }
-    }
+      });
+    }, $.Deferred().resolve());
 
     // If the focus hasn't be changed by the ajax commands, try to refocus the
     // triggering element or one of its parents if that element does not exist
@@ -1338,7 +1350,41 @@
           document.styleSheets[0].addImport(match[1]);
         } while (match);
       }
+    },
+
+    add_js: function (ajax, response) {
+      var deferred = $.Deferred();
+
+      var scriptsSrc = response.data.map(function (script) {
+
+        loadjs(script.src, script.src, {
+          async: !!script.async,
+          before: function (path, scriptEl) {
+            var selector = 'body';
+            if (script.selector) {
+              selector = script.selector;
+            }
+            if (script.defer) {
+              scriptEl.defer = true;
+            }
+            $(selector).append(scriptEl);
+
+            /* return `false` to bypass default DOM insertion mechanism */
+            return false;
+          }
+        });
+
+        return script.src;
+      });
+
+      loadjs.ready(scriptsSrc, {
+        success: function () {
+          deferred.resolve();
+        }
+      });
+
+      return deferred.promise();
     }
   };
 
-})(jQuery, window, Drupal, drupalSettings);
+})(jQuery, window, Drupal, drupalSettings, loadjs);
diff --git a/core/modules/quickedit/js/quickedit.js b/core/modules/quickedit/js/quickedit.js
index 40bdd3e609..e9804faad4 100644
--- a/core/modules/quickedit/js/quickedit.js
+++ b/core/modules/quickedit/js/quickedit.js
@@ -517,8 +517,8 @@
     });
     // Implement a scoped insert AJAX command: calls the callback after all AJAX
     // command functions have been executed (hence the deferred calling).
-    var realInsert = Drupal.AjaxCommands.prototype.insert;
-    loadEditorsAjax.commands.insert = function (ajax, response, status) {
+    var realInsert = Drupal.AjaxCommands.prototype.add_js;
+    loadEditorsAjax.commands.add_js = function (ajax, response, status) {
       _.defer(callback);
       realInsert(ajax, response, status);
     };
diff --git a/core/modules/quickedit/src/Tests/QuickEditLoadingTest.php b/core/modules/quickedit/src/Tests/QuickEditLoadingTest.php
index 41f26a841f..cc0ba0fde7 100644
--- a/core/modules/quickedit/src/Tests/QuickEditLoadingTest.php
+++ b/core/modules/quickedit/src/Tests/QuickEditLoadingTest.php
@@ -198,7 +198,7 @@ public function testUserWithPermission() {
     // First command: settings.
     $this->assertIdentical('settings', $ajax_commands[0]['command'], 'The first AJAX command is a settings command.');
     // Second command: insert libraries into DOM.
-    $this->assertIdentical('insert', $ajax_commands[1]['command'], 'The second AJAX command is an append command.');
+    $this->assertIdentical('add_js', $ajax_commands[1]['command'], 'The second AJAX command is an append command.');
     $this->assertTrue(in_array('quickedit/quickedit.inPlaceEditor.form', explode(',', $ajax_commands[0]['settings']['ajaxPageState']['libraries'])), 'The quickedit.inPlaceEditor.form library is loaded.');
 
     // Retrieving the form for this field should result in a 200 response,
