diff --git a/core/core.services.yml b/core/core.services.yml
index be54cc82ecf35a8f48e703c6644d2f4c83ebfba8..c08088c36a68ff060fbf38acc69a3f3d42ccc17e 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -1676,7 +1676,7 @@ services:
     arguments: [ '@asset.query_string', '@file_url_generator' ]
   asset.css.collection_optimizer:
     class: Drupal\Core\Asset\CssCollectionOptimizerLazy
-    arguments: [ '@asset.css.collection_grouper', '@asset.css.optimizer', '@theme.manager', '@library.dependency_resolver', '@request_stack', '@file_system', '@config.factory', '@file_url_generator', '@datetime.time', '@language_manager']
+    arguments: [ '@asset.css.collection_grouper', '@asset.css.optimizer', '@theme.manager', '@library.dependency_resolver', '@request_stack', '@file_system', '@config.factory', '@file_url_generator', '@datetime.time', '@language_manager', '@asset.css.dumper', '@state']
   asset.css.optimizer:
     class: Drupal\Core\Asset\CssOptimizer
     arguments: ['@file_url_generator']
@@ -1690,7 +1690,7 @@ services:
     arguments: [ '@asset.query_string','@file_url_generator', '@datetime.time' ]
   asset.js.collection_optimizer:
     class: Drupal\Core\Asset\JsCollectionOptimizerLazy
-    arguments: [ '@asset.js.collection_grouper', '@asset.js.optimizer', '@theme.manager', '@library.dependency_resolver', '@request_stack', '@file_system', '@config.factory', '@file_url_generator', '@datetime.time', '@language_manager']
+    arguments: [ '@asset.js.collection_grouper', '@asset.js.optimizer', '@theme.manager', '@library.dependency_resolver', '@request_stack', '@file_system', '@config.factory', '@file_url_generator', '@datetime.time', '@language_manager', '@asset.css.dumper', '@state']
   asset.js.optimizer:
     class: Drupal\Core\Asset\JsOptimizer
     arguments: ['@logger.channel.default']
diff --git a/core/lib/Drupal/Core/Asset/CssCollectionOptimizerLazy.php b/core/lib/Drupal/Core/Asset/CssCollectionOptimizerLazy.php
index 8eed354b552dab4aace0099ed68146be74d7c71d..243e604b13d94c6454b2f1aabce92aa6a32a6567 100644
--- a/core/lib/Drupal/Core/Asset/CssCollectionOptimizerLazy.php
+++ b/core/lib/Drupal/Core/Asset/CssCollectionOptimizerLazy.php
@@ -8,6 +8,7 @@
 use Drupal\Core\File\FileSystemInterface;
 use Drupal\Core\File\FileUrlGeneratorInterface;
 use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\State\StateInterface;
 use Drupal\Core\Theme\ThemeManagerInterface;
 use Symfony\Component\HttpFoundation\RequestStack;
 
@@ -18,6 +19,20 @@ class CssCollectionOptimizerLazy implements AssetCollectionGroupOptimizerInterfa
 
   use AssetGroupSetHashTrait;
 
+  /**
+   * An asset dumper.
+   *
+   * @var \Drupal\Core\Asset\AssetDumper
+   */
+  protected $dumper;
+
+  /**
+   * The state key/value store.
+   *
+   * @var \Drupal\Core\State\StateInterface
+   */
+  protected $state;
+
   /**
    * Constructs a CssCollectionOptimizerLazy.
    *
@@ -53,7 +68,11 @@ public function __construct(
     protected readonly FileUrlGeneratorInterface $fileUrlGenerator,
     protected readonly TimeInterface $time,
     protected readonly LanguageManagerInterface $languageManager,
-  ) {}
+    AssetDumperInterface $dumper,
+    StateInterface $state
+  ) {
+    $this->dumper = $dumper;
+    $this->state = $state;}
 
   /**
    * {@inheritdoc}
@@ -88,7 +107,49 @@ public function optimize(array $css_assets, array $libraries) {
           // we must know the entire set of libraries used to generate all CSS
           // groups, whether or not files in a group are from a particular
           // library or not.
-          $css_assets[$order]['preprocessed'] = TRUE;
+          $key = $this->generateHash($css_group);
+            $uri = '';
+            if (isset($map[$key])) {
+              $uri = $map[$key];
+            }
+            if (empty($uri) || !file_exists($uri)) {
+              // Optimize each asset within the group.
+              $data = '';
+              $current_license = FALSE;
+              foreach ($css_group['items'] as $css_asset) {
+                // Ensure license information is available as a comment after
+                // optimization.
+                if ($css_asset['license'] !== $current_license) {
+                  $data .= "/* @license " . $css_asset['license']['name'] . " " . $css_asset['license']['url'] . " */\n";
+                }
+                $current_license = $css_asset['license'];
+                $data .= $this->optimizer->optimize($css_asset);
+              }
+              // Per the W3C specification at
+              // http://www.w3.org/TR/REC-CSS2/cascade.html#at-import, @import
+              // rules must precede any other style, so we move those to the
+              // top. The regular expression is expressed in NOWDOC since it is
+              // detecting backslashes as well as single and double quotes. It
+              // is difficult to read when represented as a quoted string.
+              $regexp = <<<'REGEXP'
+/@import\s*(?:'(?:\\'|.)*'|"(?:\\"|.)*"|url\(\s*(?:\\[\)\'\"]|[^'")])*\s*\)|url\(\s*'(?:\'|.)*'\s*\)|url\(\s*"(?:\"|.)*"\s*\)).*;/iU
+REGEXP;
+              preg_match_all($regexp, $data, $matches);
+              $data = preg_replace($regexp, '', $data);
+              $data = implode('', $matches[0]) . (!empty($matches[0]) ? "\n" : '') . $data;
+              // Dump the optimized CSS for this group into an aggregate file.
+              $uri = $this->dumper->dump($data, 'css');
+              // Set the URI for this group's aggregate file.
+              $css_assets[$order]['data'] = $uri;
+              // Persist the URI for this aggregate file.
+              $map[$key] = $uri;
+              $this->state->set('drupal_css_cache_files', $map);
+            }
+            else {
+              // Use the persisted URI for the optimized CSS file.
+              $css_assets[$order]['data'] = $uri;
+            }
+            $css_assets[$order]['preprocessed'] = TRUE;
         }
       }
       if ($css_group['type'] === 'external') {
@@ -112,18 +173,6 @@ public function optimize(array $css_assets, array $libraries) {
       $query_args['exclude'] = UrlHelper::compressQueryParameter(implode(',', $this->dependencyResolver->getMinimalRepresentativeSubset($already_loaded)));
     }
 
-    // Generate a URL for each group of assets, but do not process them inline,
-    // this is done using optimizeGroup() when the asset path is requested.
-    foreach ($css_assets as $order => $css_asset) {
-      if (!empty($css_asset['preprocessed'])) {
-        $query = ['delta' => "$order"] + $query_args;
-        $filename = 'css_' . $this->generateHash($css_asset) . '.css';
-        $uri = 'assets://css/' . $filename;
-        $css_assets[$order]['data'] = $this->fileUrlGenerator->generateString($uri) . '?' . UrlHelper::buildQuery($query);
-      }
-      unset($css_assets[$order]['items']);
-    }
-
     return $css_assets;
   }
 
diff --git a/core/lib/Drupal/Core/Asset/JsCollectionOptimizerLazy.php b/core/lib/Drupal/Core/Asset/JsCollectionOptimizerLazy.php
index 8178c09c3e9faba5d4e1afb5e11cfe96f34b89f1..e63eff79c3070f1045852db4304b0aa49d17cd12 100644
--- a/core/lib/Drupal/Core/Asset/JsCollectionOptimizerLazy.php
+++ b/core/lib/Drupal/Core/Asset/JsCollectionOptimizerLazy.php
@@ -8,6 +8,7 @@
 use Drupal\Core\File\FileSystemInterface;
 use Drupal\Core\File\FileUrlGeneratorInterface;
 use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\State\StateInterface;
 use Drupal\Core\Theme\ThemeManagerInterface;
 use Symfony\Component\HttpFoundation\RequestStack;
 
@@ -18,6 +19,20 @@ class JsCollectionOptimizerLazy implements AssetCollectionGroupOptimizerInterfac
 
   use AssetGroupSetHashTrait;
 
+  /**
+   * An asset dumper.
+   *
+   * @var \Drupal\Core\Asset\AssetDumper
+   */
+  protected $dumper;
+
+  /**
+   * The state key/value store.
+   *
+   * @var \Drupal\Core\State\StateInterface
+   */
+  protected $state;
+
   /**
    * Constructs a JsCollectionOptimizerLazy.
    *
@@ -53,7 +68,12 @@ public function __construct(
     protected readonly FileUrlGeneratorInterface $fileUrlGenerator,
     protected readonly TimeInterface $time,
     protected readonly LanguageManagerInterface $languageManager,
-  ) {}
+    AssetDumperInterface $dumper,
+    StateInterface $state
+  ) {
+    $this->dumper = $dumper;
+    $this->state = $state;
+  }
 
   /**
    * {@inheritdoc}
@@ -85,10 +105,47 @@ public function optimize(array $js_assets, array $libraries) {
             $js_assets[$order]['data'] = $uri;
           }
           else {
-            // To reproduce the full context of assets outside of the request,
-            // we must know the entire set of libraries used to generate all CSS
-            // groups, whether or not files in a group are from a particular
-            // library or not.
+            $key = $this->generateHash($js_group);
+            $uri = '';
+            if (isset($map[$key])) {
+              $uri = $map[$key];
+            }
+            if (empty($uri) || !file_exists($uri)) {
+              // Concatenate each asset within the group.
+              $data = '';
+              $current_license = FALSE;
+              foreach ($js_group['items'] as $js_asset) {
+                // Ensure license information is available as a comment after
+                // optimization.
+                if ($js_asset['license'] !== $current_license) {
+                  $data .= "/* @license " . $js_asset['license']['name'] . " " . $js_asset['license']['url'] . " */\n";
+                }
+                $current_license = $js_asset['license'];
+                // Optimize this JS file, but only if it's not yet minified.
+                if (isset($js_asset['minified']) && $js_asset['minified']) {
+                  $data .= file_get_contents($js_asset['data']);
+                }
+                else {
+                  $data .= $this->optimizer->optimize($js_asset);
+                }
+                // Append a ';' and a newline after each JS file to prevent them
+                // from running together.
+                $data .= ";\n";
+              }
+              // Remove unwanted JS code that cause issues.
+              $data = $this->optimizer->clean($data);
+              // Dump the optimized JS for this group into an aggregate file.
+              $uri = $this->dumper->dump($data, 'js');
+              // Set the URI for this group's aggregate file.
+              $js_assets[$order]['data'] = $uri;
+              // Persist the URI for this aggregate file.
+              $map[$key] = $uri;
+              $this->state->set('system.js_cache_files', $map);
+            }
+            else {
+              // Use the persisted URI for the optimized JS file.
+              $js_assets[$order]['data'] = $uri;
+            }
             $js_assets[$order]['preprocessed'] = TRUE;
           }
           break;
@@ -120,23 +177,6 @@ public function optimize(array $js_assets, array $libraries) {
       if ($already_loaded) {
         $query_args['exclude'] = UrlHelper::compressQueryParameter(implode(',', $this->dependencyResolver->getMinimalRepresentativeSubset($already_loaded)));
       }
-
-      // Generate a URL for the group, but do not process it inline, this is
-      // done by \Drupal\system\controller\JsAssetController.
-      foreach ($js_assets as $order => $js_asset) {
-        if (!empty($js_asset['preprocessed'])) {
-          $query = [
-            'scope' => $js_asset['scope'] === 'header' ? 'header' : 'footer',
-            'delta' => "$order",
-          ] + $query_args;
-          // Add a filename prefix to mitigate ad blockers which can block
-          // any script beginning with 'ad'.
-          $filename = 'js_' . $this->generateHash($js_asset) . '.js';
-          $uri = 'assets://js/' . $filename;
-          $js_assets[$order]['data'] = $this->fileUrlGenerator->generateString($uri) . '?' . UrlHelper::buildQuery($query);
-        }
-        unset($js_assets[$order]['items']);
-      }
     }
 
     return $js_assets;