diff --git a/core/core.services.yml b/core/core.services.yml index 4693c46..89dec73 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -1500,7 +1500,7 @@ services: arguments: [ '@state' ] asset.css.collection_optimizer: class: Drupal\Core\Asset\CssCollectionOptimizerLazy - arguments: [ '@asset.css.collection_grouper', '@asset.css.optimizer', '@theme.manager' ] + arguments: [ '@asset.css.collection_grouper', '@asset.css.optimizer', '@theme.manager', '@library.dependency_resolver', '@request_stack' ] asset.css.optimizer: class: Drupal\Core\Asset\CssOptimizer asset.css.collection_grouper: @@ -1512,7 +1512,7 @@ services: arguments: [ '@state' ] asset.js.collection_optimizer: class: Drupal\Core\Asset\JsCollectionOptimizerLazy - arguments: [ '@asset.js.collection_grouper', '@asset.js.optimizer', '@theme.manager' ] + arguments: [ '@asset.js.collection_grouper', '@asset.js.optimizer', '@theme.manager', '@library.dependency_resolver', '@request_stack' ] asset.js.optimizer: class: Drupal\Core\Asset\JsOptimizer asset.js.collection_grouper: diff --git a/core/lib/Drupal/Core/Asset/AssetResolver.php b/core/lib/Drupal/Core/Asset/AssetResolver.php index 003d88c..76c30bc 100644 --- a/core/lib/Drupal/Core/Asset/AssetResolver.php +++ b/core/lib/Drupal/Core/Asset/AssetResolver.php @@ -148,7 +148,7 @@ public function getCssAssets(AttachedAssetsInterface $assets, $optimize) { // Always add a tiny value to the weight, to conserve the insertion // order. $options['weight'] += count($css) / 1000; - $options['library'] = $library; + $options['_library'] = $library; // CSS files are being keyed by the full path. $css[$options['data']] = $options; @@ -265,7 +265,10 @@ public function getJsAssets(AttachedAssetsInterface $assets, $optimize) { // Always add a tiny value to the weight, to conserve the insertion // order. $options['weight'] += count($javascript) / 1000; - $options['library'] = $library; + + // Add the library so that it can be identified from the individual + // asset. + $options['_library'] = $library; // Local and external files must keep their name as the associative // key so the same JavaScript file is not added twice. diff --git a/core/lib/Drupal/Core/Asset/CssCollectionOptimizerLazy.php b/core/lib/Drupal/Core/Asset/CssCollectionOptimizerLazy.php index 0ce60e0..0d6c9b8 100644 --- a/core/lib/Drupal/Core/Asset/CssCollectionOptimizerLazy.php +++ b/core/lib/Drupal/Core/Asset/CssCollectionOptimizerLazy.php @@ -5,6 +5,7 @@ use \Drupal\Component\Utility\UrlHelper; use \Drupal\Core\Theme\ThemeManagerInterface; +use \Symfony\Component\HttpFoundation\RequestStack; /** * Optimizes CSS assets. */ @@ -13,7 +14,7 @@ class CssCollectionOptimizerLazy implements AssetCollectionGroupOptimizerInterfa use CssCollectionOptimizerTrait; /** - * A CSS asset grouper. + * The grouper for js assets. * * @var \Drupal\Core\Asset\CssCollectionGrouper */ @@ -24,22 +25,43 @@ class CssCollectionOptimizerLazy implements AssetCollectionGroupOptimizerInterfa * * @var \Drupal\Core\Theme\ThemeManagerInterface */ - protected $themeManager; + protected $themeManager; /** - * Constructs a CssCollectionOptimizer. + * The library dependency resolver. + * + * @var \Drupal\Core\Asset\LibraryDependencyResolverInterface + */ + protected $dependencyResolver; + + /** + * The request stack. + * + * @var \Symfony\HttpFoundation\RequestStack + */ + protected $requestStack; + + /** + * Constructs a CssCollectionOptimizerLazy. * * @param \Drupal\Core\Asset\AssetCollectionGrouperInterface $grouper * The grouper for CSS assets. * @param \Drupal\Core\Asset\AssetOptimizerInterface $optimizer * The optimizer for a single CSS asset. - * @param \Drupal\Core\Theme\ThemeManagerInterface - * The Theme manager. + * @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager + * The theme manager. + * @param \Drupal\Core\Asset\LibraryDependencyResolverInterface $dependency_resolver + * The library dependency resolver. + * @param \Symfony\HttpFoundation\RequestStack $request_stack + * The request stack. + */ - public function __construct(AssetCollectionGrouperInterface $grouper, AssetOptimizerInterface $optimizer, ThemeManagerInterface $theme_manager) { + public function __construct(AssetCollectionGrouperInterface $grouper, AssetOptimizerInterface $optimizer, ThemeManagerInterface $theme_manager, LibraryDependencyResolverInterface $dependency_resolver, RequestStack $request_stack) { $this->grouper = $grouper; $this->optimizer = $optimizer; $this->themeManager = $theme_manager; + $this->dependencyResolver = $dependency_resolver; + $this->requestStack = $request_stack; } /** @@ -83,7 +105,7 @@ public function optimize(array $css_assets) { // groups, whether or not files in a group are from a particular // library or not. foreach ($css_group['items'] as $css_asset) { - $libraries[$css_asset['library']] = $css_asset['library']; + $libraries[$css_asset['_library']] = $css_asset['_library']; } $css_assets[$order]['preprocessed'] = TRUE; } @@ -97,20 +119,20 @@ public function optimize(array $css_assets) { break; } } - // Generate a URL for the group, but do not process it inline, this is - // done by \Drupal\system\controller\CssAssetController - $minimal_set = \Drupal::service('library.dependency_resolver')->getMinimalRepresentativeSubset($libraries); - $theme_name = $this->themeManager->getActiveTheme()->getName(); - $ajax_page_state = \Drupal::request()->get('ajax_page_state'); + // Generate a URL each group of assets, but do not process them inline, this + // is done using self::optimizeGroup() when the asset path is requested. + $ajax_page_state = $this->requestStack->getCurrentRequest()->query->get('ajax_page_state'); $already_loaded = isset($ajax_page_state) ? explode(',', $ajax_page_state['libraries']) : []; - $minimal_already_loaded_set = \Drupal::service('library.dependency_resolver')->getMinimalRepresentativeSubset($already_loaded); - $query = UrlHelper::buildQuery(['libraries' => $minimal_set, 'theme' => $theme_name, 'already_loaded' => $minimal_already_loaded_set]); - $path = 'public://' . 'css'; + $query_parameters = [ + 'theme' => $this->themeManager->getActiveTheme()->getName(), + 'libraries' => $this->dependencyResolver->getMinimalRepresentativeSubset($libraries), + 'already_loaded' => $this->dependencyResolver->getMinimalRepresentativeSubset($already_loaded), + ]; + $query = UrlHelper::buildQuery($query_parameters); foreach ($css_assets as $order => $css_asset) { if (!empty($css_asset['preprocessed'])) { - $filename = 'css' . '_' . $order . '_' . $key . '.' . 'css'; - // Create the css/ or js/ path within the files folder. - $uri = $path . '/' . $filename; + $filename = 'css' . '_' . $order . '_' . $key . '.css'; + $uri = 'public://css/' . $filename; $css_assets[$order]['data'] = file_create_url($uri) . '?' . $query; } } diff --git a/core/lib/Drupal/Core/Asset/JsCollectionOptimizerLazy.php b/core/lib/Drupal/Core/Asset/JsCollectionOptimizerLazy.php index cff1978..4d0df1d 100644 --- a/core/lib/Drupal/Core/Asset/JsCollectionOptimizerLazy.php +++ b/core/lib/Drupal/Core/Asset/JsCollectionOptimizerLazy.php @@ -5,6 +5,8 @@ use \Drupal\Component\Utility\UrlHelper; use \Drupal\Core\Theme\ThemeManagerInterface; +use \Symfony\Component\HttpFoundation\RequestStack; + /** * Optimizes JavaScript assets. */ @@ -27,19 +29,39 @@ class JsCollectionOptimizerLazy implements AssetCollectionGroupOptimizerInterfac protected $themeManager; /** - * Constructs a JsCollectionOptimizer. + * The library dependency resolver. + * + * @var \Drupal\Core\Asset\LibraryDependencyResolverInterface + */ + protected $dependencyResolver; + + /** + * The request stack. + * + * @var \Symfony\HttpFoundation\RequestStack + */ + protected $requestStack; + + /** + * Constructs a JsCollectionOptimizerLazy. * * @param \Drupal\Core\Asset\AssetCollectionGrouperInterface $grouper * The grouper for JS assets. - * @param \Drupal\Core\Asset\AssetOptimizerInterface + * @param \Drupal\Core\Asset\AssetOptimizerInterface $optimizer * The asset optimizer. - * @param \Drupal\Core\Theme\ThemeManagerInterface; - * The theme manger. + * @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager + * The theme manager. + * @param \Drupal\Core\Asset\LibraryDependencyResolverInterface $dependency_resolver + * The library dependency resolver. + * @param \Symfony\HttpFoundation\RequestStack $request_stack + * The request stack. */ - public function __construct(AssetCollectionGrouperInterface $grouper, AssetOptimizerInterface $optimizer, ThemeManagerInterface $theme_manager) { + public function __construct(AssetCollectionGrouperInterface $grouper, AssetOptimizerInterface $optimizer, ThemeManagerInterface $theme_manager, LibraryDependencyResolverInterface $dependency_resolver, RequestStack $request_stack) { $this->grouper = $grouper; $this->optimizer = $optimizer; $this->themeManager = $theme_manager; + $this->dependencyResolver = $dependency_resolver; + $this->requestStack = $request_stack; } /** @@ -79,7 +101,7 @@ public function optimize(array $js_assets) { // groups, whether or not files in a group are from a particular // library or not. foreach ($js_group['items'] as $js_asset) { - $libraries[$js_asset['library']] = $js_asset['library']; + $libraries[$js_asset['_library']] = $js_asset['_library']; } $js_assets[$order]['preprocessed'] = TRUE; } @@ -99,19 +121,21 @@ public function optimize(array $js_assets) { if ($libraries) { // Generate a URL for the group, but do not process it inline, this is // done by \Drupal\system\controller\JsAssetController - $minimal_set = \Drupal::service('library.dependency_resolver')->getMinimalRepresentativeSubset($libraries); - $theme_name = $this->themeManager->getActiveTheme()->getName(); - $ajax_page_state = \Drupal::request()->get('ajax_page_state'); + $ajax_page_state = $this->requestStack->getCurrentRequest()->query->get('ajax_page_state'); $already_loaded = isset($ajax_page_state) ? explode(',', $ajax_page_state['libraries']) : []; - $minimal_already_loaded_set = \Drupal::service('library.dependency_resolver')->getMinimalRepresentativeSubset($already_loaded); - $header_query = UrlHelper::buildQuery(['libraries' => $minimal_set, 'theme' => $theme_name, 'already_loaded' => $minimal_already_loaded_set, 'scope' => 'header']); - $footer_query = UrlHelper::buildQuery(['libraries' => $minimal_set, 'theme' => $theme_name, 'already_loaded' => $minimal_already_loaded_set, 'scope' => 'footer']); - $path = 'public://' . 'js'; + + $query_args = [ + 'libraries' => $this->dependencyResolver->getMinimalRepresentativeSubset($libraries), + 'theme' => $this->themeManager->getActiveTheme()->getName(), + 'already_loaded' => $this->dependencyResolver->getMinimalRepresentativeSubset($already_loaded), + ]; + $header_query = UrlHelper::buildQuery($query_args + ['scope' => 'header']); + $footer_query = UrlHelper::buildQuery($query_args + ['scope' => 'footer']); foreach ($js_assets as $order => $js_asset) { if (!empty($js_asset['preprocessed'])) { $query = $js_asset['scope'] == 'header' ? $header_query : $footer_query; - $filename = 'js' . '_' . $order . '_' . $key . '.' . 'js'; - $uri = $path . '/' . $filename; + $filename = 'js' . '_' . $order . '_' . $key . '.js'; + $uri = 'public://js' . '/' . $filename; $js_assets[$order]['data'] = file_create_url($uri) . '?' . $query; } } diff --git a/core/modules/system/src/Controller/AssetControllerBase.php b/core/modules/system/src/Controller/AssetControllerBase.php index 439da7c..6dd9a58 100644 --- a/core/modules/system/src/Controller/AssetControllerBase.php +++ b/core/modules/system/src/Controller/AssetControllerBase.php @@ -104,9 +104,9 @@ * Constructs a CssAssetController object. * * @param \Drupal\Core\Asset\LibraryDependencyResolverInterface - * The library dependency resolver. + * The library dependency resolver. * @param \Drupal\Core\Asset\AssetResolverInterface - * The Asset resolver. + * The asset resolver. * @param \Drupal\Core\Theme\ThemeInitializationInterface * The theme initializer. * @param \Drupal\Core\Theme\ThemeManagerInterface @@ -130,7 +130,7 @@ public function __construct(LibraryDependencyResolverInterface $library_dependen } /** - * Generates a CSS aggregate, given a filename. + * Generates an aggregate, given a filename. * * @param \Symfony\Component\HttpFoundation\Request $request * The request object. @@ -138,7 +138,7 @@ public function __construct(LibraryDependencyResolverInterface $library_dependen * The file to deliver. * * @return \Symfony\Component\HttpFoundation\BinaryFileResponse|\Symfony\Component\HttpFoundation\Response - * The transferred file as response or some error response. + * The transferred file as response. * * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException * Thrown when the filename is invalid. @@ -176,16 +176,10 @@ public function deliver(Request $request, $file_name) { // the URL. This can be for three reasons: // 1. Someone has requested an outdated URL, i.e. from a cached page, which // matches a different version of the code base. - // 2. Someone has requrested an outdated URL during a deployment. This is + // 2. Someone has requested an outdated URL during a deployment. This is // the same case as #1 but a much shorter window. // 3. Someone is attempting to craft an invalid URL in order to conduct a // denial of service attack on the site. - // - // @todo: we could potentially hash the library definitions with the secret - // key to create an HMAC, so that only valid combinations of libraries are - // allowed. - // For now treat all of these the same, but if there's no match, don't write - // to the filesystem. $match = TRUE; $uri = 'public://' . $this->assetType . '/' . $file_name; @@ -201,8 +195,13 @@ public function deliver(Request $request, $file_name) { // Either way, we didn't get a match. $match = FALSE; } - $headers = [ - 'Content-Type' => $this->contentType, + + if (!file_exists($uri)) { + $data = $this->optimizer->optimizeGroup($group); + // Dump the optimized CSS for this group into an aggregate file. + if ($match) { + $uri = $this->dumper->dumpToUri($data, $this->assetType, $uri); + } // Headers sent from PHP can never perfectly match those sent when the // file is served by the filesystem, so ensure this request does not get // cached in either the browser or reverse proxies. Subsequent requests @@ -211,25 +210,11 @@ public function deliver(Request $request, $file_name) { // cached from PHP, while another is serving a version cached from disk. // Should there be any discrepancy in behaviour between those files, this // can make debugging very difficult. - 'Cache-control' => $this->cacheControl, - ]; - - if (!file_exists($uri)) { - $data = $this->optimizer->optimizeGroup($group); - // Dump the optimized CSS for this group into an aggregate file. - if ($match) { - $uri = $this->dumper->dumpToUri($data, $this->assetType, $uri); - } - $headers = [ - 'Content-Type' => $this->contentType, - 'Cache-control' => $this->cacheControl, - ]; - // Generate response. - $response = new Response($data, 200, $headers); + $response = new Response($data, 200, ['Cache-control' => $this->cacheControl, 'Content-Type' => $this->contentType]); return $response; } else { - return new BinaryFileResponse($uri, 200, $headers); + return new BinaryFileResponse($uri, 200, ['Cache-control' => $this->cacheControl]); } } diff --git a/core/modules/system/src/Routing/AssetRoutes.php b/core/modules/system/src/Routing/AssetRoutes.php index 4a46446..5355653 100644 --- a/core/modules/system/src/Routing/AssetRoutes.php +++ b/core/modules/system/src/Routing/AssetRoutes.php @@ -1,10 +1,5 @@