core/includes/common.inc | 297 +------------------- .../Core/Asset/{CssDumper.php => AssetDumper.php} | 28 +- .../lib/Drupal/Core/Asset/AssetDumperInterface.php | 4 +- .../lib/Drupal/Core/Asset/CssCollectionGrouper.php | 9 + .../Drupal/Core/Asset/CssCollectionOptimizer.php | 12 +- core/lib/Drupal/Core/Asset/JsCollectionGrouper.php | 80 ++++++ .../Drupal/Core/Asset/JsCollectionOptimizer.php | 139 +++++++++ .../lib/Drupal/Core/Asset/JsCollectionRenderer.php | 97 +++++++ .../Drupal/system/Tests/Common/JavaScriptTest.php | 44 ++- core/modules/system/system.module | 4 - .../Core/Asset/CssCollectionRendererUnitTest.php | 2 +- 11 files changed, 397 insertions(+), 319 deletions(-) diff --git a/core/includes/common.inc b/core/includes/common.inc index 0ab390a..eff6d1d 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -29,7 +29,10 @@ use Drupal\Core\Asset\CssCollectionOptimizer; use Drupal\Core\Asset\CssCollectionGrouper; use Drupal\Core\Asset\CssOptimizer; -use Drupal\Core\Asset\CssDumper; +use Drupal\Core\Asset\JsCollectionRenderer; +use Drupal\Core\Asset\JsCollectionOptimizer; +use Drupal\Core\Asset\JsCollectionGrouper; +use Drupal\Core\Asset\AssetDumper; /** * @file @@ -1952,7 +1955,7 @@ function drupal_pre_render_styles($elements) { // Aggregate the CSS if necessary, but only during normal site operation. if (!defined('MAINTENANCE_MODE') && config('system.performance')->get('css.preprocess')) { - $optimizer = new CssCollectionOptimizer(new CssCollectionGrouper(), new CssOptimizer(), new CssDumper()); + $optimizer = new CssCollectionOptimizer(new CssCollectionGrouper(), new CssOptimizer(), new AssetDumper()); $css_assets = $optimizer->optimize($css_assets); } @@ -2576,214 +2579,17 @@ function drupal_merge_js_settings($settings_items) { * @see drupal_get_js() */ function drupal_pre_render_scripts($elements) { - // Group and aggregate the items. - if (isset($elements['#group_callback'])) { - $elements['#groups'] = $elements['#group_callback']($elements['#items']); - } - if (isset($elements['#aggregate_callback'])) { - $elements['#aggregate_callback']($elements['#groups']); - } - - // A dummy query-string is added to filenames, to gain control over - // browser-caching. The string changes on every update or full cache - // flush, forcing browsers to load a new copy of the files, as the - // URL changed. Files that should not be cached (see drupal_add_js()) - // get REQUEST_TIME as query-string instead, to enforce reload on every - // page request. - $default_query_string = variable_get('css_js_query_string', '0'); - - // For inline JavaScript to validate as XHTML, all JavaScript containing - // XHTML needs to be wrapped in CDATA. To make that backwards compatible - // with HTML 4, we need to comment out the CDATA-tag. - $embed_prefix = "\n\n"; - - // Since JavaScript may look for arguments in the URL and act on them, some - // third-party code might require the use of a different query string. - $js_version_string = variable_get('drupal_js_version_query_string', 'v='); - - // Defaults for each SCRIPT element. - $element_defaults = array( - '#type' => 'html_tag', - '#tag' => 'script', - '#value' => '', - ); + $renderer = new JsCollectionRenderer(); + $js_assets = $elements['#items']; - // Loop through each group. - foreach ($elements['#groups'] as $group) { - // If a group of files has been aggregated into a single file, - // $group['data'] contains the URI of the aggregate file. Add a single - // script element for this file. - if ($group['type'] == 'file' && isset($group['data'])) { - $element = $element_defaults; - $element['#attributes']['src'] = file_create_url($group['data']); - $element['#browsers'] = $group['browsers']; - $elements[] = $element; - } - // For non-file types, and non-aggregated files, add a script element per - // item. - else { - foreach ($group['items'] as $item) { - // Element properties that do not depend on item type. - $element = $element_defaults; - $element['#browsers'] = $item['browsers']; - - // Element properties that depend on item type. - switch ($item['type']) { - case 'setting': - $element['#value_prefix'] = $embed_prefix; - $element['#value'] = 'var drupalSettings = ' . drupal_json_encode(drupal_merge_js_settings($item['data'])) . ";"; - $element['#value_suffix'] = $embed_suffix; - break; - - case 'inline': - $element['#value_prefix'] = $embed_prefix; - $element['#value'] = $item['data']; - $element['#value_suffix'] = $embed_suffix; - break; - - case 'file': - $query_string = empty($item['version']) ? $default_query_string : $js_version_string . $item['version']; - $query_string_separator = (strpos($item['data'], '?') !== FALSE) ? '&' : '?'; - $element['#attributes']['src'] = file_create_url($item['data']) . $query_string_separator . ($item['cache'] ? $query_string : REQUEST_TIME); - break; - - case 'external': - $element['#attributes']['src'] = $item['data']; - break; - } - - // Attributes may only be set if this script is output independently. - if (!empty($element['#attributes']['src']) && !empty($item['attributes'])) { - $element['#attributes'] += $item['attributes']; - } - - $elements[] = $element; - } - } + // Aggregate the JavaScript if necessary, but only during normal site + // operation. + if (!defined('MAINTENANCE_MODE') && config('system.performance')->get('js.preprocess')) { + $optimizer = new JsCollectionOptimizer(new JsCollectionGrouper(), new AssetDumper()); + $js_assets = $optimizer->optimize($js_assets); } - return $elements; -} - -/** - * Default callback to group JavaScript items. - * - * This function arranges the JavaScript items that are in the #items property - * of the scripts element into groups. When aggregation is enabled, files within - * a group are aggregated into a single file, significantly improving page - * loading performance by minimizing network traffic overhead. - * - * This function puts multiple items into the same group if they are groupable - * and if they are for the same browsers. Items of the 'file' type are groupable - * if their 'preprocess' flag is TRUE. Items of the 'inline', 'settings', or - * 'external' type are not groupable. - * - * This function also ensures that the process of grouping items does not change - * their relative order. This requirement may result in multiple groups for the - * same type and browsers, if needed to accommodate other items in - * between. - * - * @param $javascript - * An array of JavaScript items, as returned by drupal_add_js(), but after - * alteration performed by drupal_get_js(). - * - * @return - * An array of JavaScript groups. Each group contains the same keys (e.g., - * 'data', etc.) as a JavaScript item from the $javascript parameter, with the - * value of each key applying to the group as a whole. Each group also - * contains an 'items' key, which is the subset of items from $javascript that - * are in the group. - * - * @see drupal_pre_render_scripts() - */ -function drupal_group_js($javascript) { - $groups = array(); - // If a group can contain multiple items, we track the information that must - // be the same for each item in the group, so that when we iterate the next - // item, we can determine if it can be put into the current group, or if a - // new group needs to be made for it. - $current_group_keys = NULL; - $index = -1; - foreach ($javascript as $item) { - // The browsers for which the JavaScript item needs to be loaded is part of - // the information that determines when a new group is needed, but the order - // of keys in the array doesn't matter, and we don't want a new group if all - // that's different is that order. - ksort($item['browsers']); - - switch ($item['type']) { - case 'file': - // Group file items if their 'preprocess' flag is TRUE. - // Help ensure maximum reuse of aggregate files by only grouping - // together items that share the same 'group' value and 'every_page' - // flag. See drupal_add_js() for details about that. - $group_keys = $item['preprocess'] ? array($item['type'], $item['group'], $item['every_page'], $item['browsers']) : FALSE; - break; - - case 'external': - case 'setting': - case 'inline': - // Do not group external, settings, and inline items. - $group_keys = FALSE; - break; - } - - // If the group keys don't match the most recent group we're working with, - // then a new group must be made. - if ($group_keys !== $current_group_keys) { - $index++; - // Initialize the new group with the same properties as the first item - // being placed into it. The item's 'data' and 'weight' properties are - // unique to the item and should not be carried over to the group. - $groups[$index] = $item; - unset($groups[$index]['data'], $groups[$index]['weight']); - $groups[$index]['items'] = array(); - $current_group_keys = $group_keys ? $group_keys : NULL; - } - - // Add the item to the current group. - $groups[$index]['items'][] = $item; - } - - return $groups; -} - -/** - * Default callback to aggregate JavaScript files. - * - * Having the browser load fewer JavaScript files results in much faster page - * loads than when it loads many small files. This function aggregates files - * within the same group into a single file unless the site-wide setting to do - * so is disabled (commonly the case during site development). To optimize - * download, it also compresses the aggregate files by removing comments, - * whitespace, and other unnecessary content. - * - * @param $js_groups - * An array of JavaScript groups as returned by drupal_group_js(). For each - * group that is aggregated, this function sets the value of the group's - * 'data' key to the URI of the aggregate file. - * - * @see drupal_group_js() - * @see drupal_pre_render_scripts() - */ -function drupal_aggregate_js(&$js_groups) { - // Only aggregate during normal site operation. - if (defined('MAINTENANCE_MODE')) { - $preprocess_js = FALSE; - } - else { - $config = config('system.performance'); - $preprocess_js = $config->get('js.preprocess'); - } - - if ($preprocess_js) { - foreach ($js_groups as $key => $group) { - if ($group['type'] == 'file' && $group['preprocess']) { - $js_groups[$key]['data'] = drupal_build_js_cache($group['items']); - } - } - } + return $renderer->render($js_assets); } /** @@ -3286,83 +3092,6 @@ function drupal_add_tabledrag($table_id, $action, $relationship, $group, $subgro } /** - * Aggregates JavaScript files into a cache file in the files directory. - * - * The file name for the JavaScript cache file is generated from the hash of - * the aggregated contents of the files in $files. This forces proxies and - * browsers to download new JavaScript when the JavaScript changes. - * - * The cache file name is retrieved on a page load via a lookup variable that - * contains an associative array. The array key is the hash of the names in - * $files while the value is the cache file name. The cache file is generated - * in two cases. First, if there is no file name value for the key, which will - * happen if a new file name has been added to $files or after the lookup - * variable is emptied to force a rebuild of the cache. Second, the cache file - * is generated if it is missing on disk. Old cache files are not deleted - * immediately when the lookup variable is emptied, but are deleted after a set - * period by drupal_delete_file_if_stale(). This ensures that files referenced - * by a cached page will still be available. - * - * @param $files - * An array of JavaScript files to aggregate and compress into one file. - * - * @return - * The URI of the cache file, or FALSE if the file could not be saved. - */ -function drupal_build_js_cache($files) { - $contents = ''; - $uri = ''; - $map = Drupal::state()->get('system.js_cache_files') ?: array(); - // Create a new array so that only the file names are used to create the hash. - // This prevents new aggregates from being created unnecessarily. - $js_data = array(); - foreach ($files as $file) { - $js_data[] = $file['data']; - } - $key = hash('sha256', serialize($js_data)); - if (isset($map[$key])) { - $uri = $map[$key]; - } - - if (empty($uri) || !file_exists($uri)) { - // Build aggregate JS file. - foreach ($files as $info) { - if ($info['preprocess']) { - // Append a ';' and a newline after each JS file to prevent them from running together. - $contents .= file_get_contents($info['data']) . ";\n"; - } - } - // Prefix filename to prevent blocking by firewalls which reject files - // starting with "ad*". - $filename = 'js_' . Crypt::hashBase64($contents) . '.js'; - // Create the js/ within the files folder. - $jspath = 'public://js'; - $uri = $jspath . '/' . $filename; - // Create the JS file. - file_prepare_directory($jspath, FILE_CREATE_DIRECTORY); - if (!file_exists($uri) && !file_unmanaged_save_data($contents, $uri, FILE_EXISTS_REPLACE)) { - return FALSE; - } - // If JS gzip compression is enabled and the zlib extension is available - // then create a gzipped version of this file. This file is served - // conditionally to browsers that accept gzip using .htaccess rules. - // It's possible that the rewrite rules in .htaccess aren't working on this - // server, but there's no harm (other than the time spent generating the - // file) in generating the file anyway. Sites on servers where rewrite rules - // aren't working can set js.gzip to FALSE in order to skip - // generating a file that won't be used. - if (config('system.performance')->get('js.gzip') && extension_loaded('zlib')) { - if (!file_exists($uri . '.gz') && !file_unmanaged_save_data(gzencode($contents, 9, FORCE_GZIP), $uri . '.gz', FILE_EXISTS_REPLACE)) { - return FALSE; - } - } - $map[$key] = $uri; - Drupal::state()->set('system.js_cache_files', $map); - } - return $uri; -} - -/** * Deletes old cached JavaScript files and variables. */ function drupal_clear_js_cache() { diff --git a/core/lib/Drupal/Core/Asset/CssDumper.php b/core/lib/Drupal/Core/Asset/AssetDumper.php similarity index 55% rename from core/lib/Drupal/Core/Asset/CssDumper.php rename to core/lib/Drupal/Core/Asset/AssetDumper.php index 06fe54c..6e8c0e5 100644 --- a/core/lib/Drupal/Core/Asset/CssDumper.php +++ b/core/lib/Drupal/Core/Asset/AssetDumper.php @@ -1,7 +1,7 @@ get('css.gzip') && extension_loaded('zlib')) { + if (config('system.performance')->get($file_extension . '.gzip') && extension_loaded('zlib')) { if (!file_exists($uri . '.gz') && !file_unmanaged_save_data(gzencode($data, 9, FORCE_GZIP), $uri . '.gz', FILE_EXISTS_REPLACE)) { return FALSE; } diff --git a/core/lib/Drupal/Core/Asset/AssetDumperInterface.php b/core/lib/Drupal/Core/Asset/AssetDumperInterface.php index f0d8ffe..6ee2ef2 100644 --- a/core/lib/Drupal/Core/Asset/AssetDumperInterface.php +++ b/core/lib/Drupal/Core/Asset/AssetDumperInterface.php @@ -16,9 +16,11 @@ * * @param string $data * An (optimized) asset's contents. + * @param string $file_extension + * The file extension of this asset. * @return string * An URI to access the dumped asset. */ - public function dump($data); + public function dump($data, $file_extension); } diff --git a/core/lib/Drupal/Core/Asset/CssCollectionGrouper.php b/core/lib/Drupal/Core/Asset/CssCollectionGrouper.php index 2e59d18..93ce0b0 100644 --- a/core/lib/Drupal/Core/Asset/CssCollectionGrouper.php +++ b/core/lib/Drupal/Core/Asset/CssCollectionGrouper.php @@ -15,6 +15,15 @@ class CssCollectionGrouper implements AssetCollectionGrouperInterface { /** * {@inheritdoc} + * + * Puts multiple items into the same group if they are groupable and if they + * are for the same 'media' and 'browsers'. Items of the 'file' type are + * groupable if their 'preprocess' flag is TRUE, items of the 'inline' type + * are always groupable, and items of the 'external' type are never groupable. + * + * Also ensures that the process of grouping items does not change their + * relative order. This requirement may result in multiple groups for the same + * type, media, and browsers, if needed to accommodate other items in between. */ public function group(array $css_assets) { $groups = array(); diff --git a/core/lib/Drupal/Core/Asset/CssCollectionOptimizer.php b/core/lib/Drupal/Core/Asset/CssCollectionOptimizer.php index 1ea1495..3a2abc7 100644 --- a/core/lib/Drupal/Core/Asset/CssCollectionOptimizer.php +++ b/core/lib/Drupal/Core/Asset/CssCollectionOptimizer.php @@ -29,9 +29,9 @@ class CssCollectionOptimizer implements AssetCollectionOptimizerInterface { protected $optimizer; /** - * A CSS asset dumper. + * An asset dumper. * - * @var \Drupal\Core\Asset\CssDumper object. + * @var \Drupal\Core\Asset\AssetDumper object. */ protected $dumper; @@ -111,7 +111,7 @@ public function optimize(array $css_assets) { $data = preg_replace($regexp, '', $data); $data = implode('', $matches[0]) . $data; // Dump the optimized CSS for this group into an aggregate file. - $uri = $this->dumper->dump($data); + $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. @@ -122,6 +122,7 @@ public function optimize(array $css_assets) { // Use the persisted URI for the optimized CSS file. $css_assets[$order]['data'] = $uri; } + $css_assets[$order]['preprocessed'] = TRUE; } break; @@ -138,7 +139,8 @@ public function optimize(array $css_assets) { case 'external': // We don't do any aggregation and hence also no caching for external // CSS assets. - $css_assets[$order] = $css_group; + $uri = $css_group['items'][0]['data']; + $css_assets[$order]['data'] = $uri; break; } } @@ -149,7 +151,7 @@ public function optimize(array $css_assets) { /** * Generate a hash for a given group of CSS assets. */ - protected function generateHash($css_group) { + protected function generateHash(array $css_group) { $css_data = array(); foreach ($css_group['items'] as $css_file) { $css_data[] = $css_file['data']; diff --git a/core/lib/Drupal/Core/Asset/JsCollectionGrouper.php b/core/lib/Drupal/Core/Asset/JsCollectionGrouper.php new file mode 100644 index 0000000..fb039b8 --- /dev/null +++ b/core/lib/Drupal/Core/Asset/JsCollectionGrouper.php @@ -0,0 +1,80 @@ +grouper = $grouper; + $this->dumper = $dumper; + } + + /** + * {@inheritdoc} + * + * The cache file name is retrieved on a page load via a lookup variable that + * contains an associative array. The array key is the hash of the names in + * $files while the value is the cache file name. The cache file is generated + * in two cases. First, if there is no file name value for the key, which will + * happen if a new file name has been added to $files or after the lookup + * variable is emptied to force a rebuild of the cache. Second, the cache file + * is generated if it is missing on disk. Old cache files are not deleted + * immediately when the lookup variable is emptied, but are deleted after a + * set period by drupal_delete_file_if_stale(). This ensures that files + * referenced by a cached page will still be available. + */ + public function optimize(array $js_assets) { + // Group the assets. + $js_groups = $this->grouper->group($js_assets); + + // Now optimize (concatenate, not minify) and dump each asset group, unless + // that was already done, in which case it should appear in + // system.js_cache_files. + // Drupal contrib can override this default JS aggregator to keep the same + // grouping, optimizing and dumping, but change the strategy that is used to + // determine when the aggregate should be rebuilt (e.g. mtime, HTTPS …). + $map = Drupal::state()->get('system.js_cache_files') ?: array(); + $js_assets = array(); + foreach ($js_groups as $order => $js_group) { + // We have to return a single asset, not a group of assets. It is now up + // to one of the pieces of code in the switch statement below to set the + // 'data' property to the appropriate value. + $js_assets[$order] = $js_group; + unset($js_assets[$order]['items']); + + switch ($js_group['type']) { + case 'file': + // No preprocessing, single JS asset: just use the existing URI. + if (!$js_group['preprocess']) { + $uri = $js_group['items'][0]['data']; + $js_assets[$order]['data'] = $uri; + } + // Preprocess (aggregate), unless the aggregate file already exists. + else { + $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 = ''; + foreach ($js_group['items'] as $js_asset) { + $data .= file_get_contents($js_asset['data']); + // Append a ';' and a newline after each JS file to prevent them from running together. + $data .= ";\n"; + } + // 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; + Drupal::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; + + case 'external': + case 'setting': + case 'inline': + // We don't do any aggregation and hence also no caching for external, + // setting or inline JS assets. + $uri = $js_group['items'][0]['data']; + $js_assets[$order]['data'] = $uri; + break; + } + } + + return $js_assets; + } + + /** + * Generate a hash for a given group of JS assets. + */ + protected function generateHash(array $js_group) { + $js_data = array(); + foreach ($js_group['items'] as $js_file) { + $js_data[] = $js_file['data']; + } + return hash('sha256', serialize($js_data)); + } +} diff --git a/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php b/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php new file mode 100644 index 0000000..6f9323e --- /dev/null +++ b/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php @@ -0,0 +1,97 @@ +\n"; + + // Since JavaScript may look for arguments in the URL and act on them, some + // third-party code might require the use of a different query string. + $js_version_string = variable_get('drupal_js_version_query_string', 'v='); + + // Defaults for each SCRIPT element. + $element_defaults = array( + '#type' => 'html_tag', + '#tag' => 'script', + '#value' => '', + ); + + // Loop through all JS assets. + foreach ($js_assets as $js_asset) { + // Element properties that do not depend on JS asset type. + $element = $element_defaults; + $element['#browsers'] = $js_asset['browsers']; + + // Element properties that depend on item type. + switch ($js_asset['type']) { + case 'setting': + $element['#value_prefix'] = $embed_prefix; + $element['#value'] = 'var drupalSettings = ' . drupal_json_encode(drupal_merge_js_settings($js_asset['data'])) . ";"; + $element['#value_suffix'] = $embed_suffix; + break; + + case 'inline': + $element['#value_prefix'] = $embed_prefix; + $element['#value'] = $js_asset['data']; + $element['#value_suffix'] = $embed_suffix; + break; + + case 'file': + $query_string = empty($js_asset['version']) ? $default_query_string : $js_version_string . $js_asset['version']; + $query_string_separator = (strpos($js_asset['data'], '?') !== FALSE) ? '&' : '?'; + $element['#attributes']['src'] = file_create_url($js_asset['data']); + // Only add the cache-busting query string if this isn't an aggregate + // file. + if (!isset($js_asset['preprocessed'])) { + $element['#attributes']['src'] .= $query_string_separator . ($js_asset['cache'] ? $query_string : REQUEST_TIME); + } + break; + + case 'external': + $element['#attributes']['src'] = $js_asset['data']; + break; + + default: + throw new \Exception('Invalid JS asset type.'); + } + + // Attributes may only be set if this script is output independently. + if (!empty($element['#attributes']['src']) && !empty($js_asset['attributes'])) { + $element['#attributes'] += $js_asset['attributes']; + } + + $elements[] = $element; + } + + return $elements; + } + +} diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/JavaScriptTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/JavaScriptTest.php index 0a457a6..a00f2b5 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Common/JavaScriptTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Common/JavaScriptTest.php @@ -8,6 +8,7 @@ namespace Drupal\system\Tests\Common; use Drupal\simpletest\WebTestBase; +use Drupal\Component\Utility\Crypt; /** * Tests the JavaScript system. @@ -345,8 +346,8 @@ function testAggregation() { $js_items = drupal_add_js(); $javascript = drupal_get_js(); $expected = implode("\n", array( - '', - '', + '', + '', )); $this->assertTrue(strpos($javascript, $expected) !== FALSE, 'JavaScript is aggregated in the expected groups and order.'); } @@ -365,10 +366,14 @@ function testAggregationOrder() { drupal_add_js('core/misc/autocomplete.js'); $js_items = drupal_add_js(); - drupal_build_js_cache(array( - 'core/misc/ajax.js' => $js_items['core/misc/ajax.js'], - 'core/misc/autocomplete.js' => $js_items['core/misc/autocomplete.js'] - )); + $scripts_html = array( + '#type' => 'scripts', + '#items' => array( + 'core/misc/ajax.js' => $js_items['core/misc/ajax.js'], + 'core/misc/autocomplete.js' => $js_items['core/misc/autocomplete.js'] + ) + ); + drupal_render($scripts_html); // Store the expected key for the first item in the cache. $cache = array_keys(\Drupal::state()->get('system.js_cache_files') ?: array()); @@ -384,10 +389,14 @@ function testAggregationOrder() { // Rebuild the cache. $js_items = drupal_add_js(); - drupal_build_js_cache(array( - 'core/misc/ajax.js' => $js_items['core/misc/ajax.js'], - 'core/misc/autocomplete.js' => $js_items['core/misc/autocomplete.js'] - )); + $scripts_html = array( + '#type' => 'scripts', + '#items' => array( + 'core/misc/ajax.js' => $js_items['core/misc/ajax.js'], + 'core/misc/autocomplete.js' => $js_items['core/misc/autocomplete.js'] + ) + ); + drupal_render($scripts_html); // Compare the expected key for the first file to the current one. $cache = array_keys(\Drupal::state()->get('system.js_cache_files') ?: array()); @@ -562,4 +571,19 @@ function testAddJsFileWithQueryString() { $query_string = variable_get('css_js_query_string', '0'); $this->assertRaw(drupal_get_path('module', 'node') . '/node.js?' . $query_string, 'Query string was appended correctly to js.'); } + + /** + * Calculates the aggregated file name of a group of JavaScript assets. + * + * @see testAggregation() + * @see testAggregationOrder() + */ + protected function calculateAggregateFilename($js_assets) { + $data = ''; + foreach ($js_assets as $js_asset) { + $data .= file_get_contents($js_asset['data']) . ";\n"; + } + return file_create_url('public://js/js_' . Crypt::hashBase64($data) . '.js'); + } + } diff --git a/core/modules/system/system.module b/core/modules/system/system.module index 125f807..b06b621 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -284,14 +284,10 @@ function system_element_info() { $types['styles'] = array( '#items' => array(), '#pre_render' => array('drupal_pre_render_styles'), - '#group_callback' => 'drupal_group_css', - '#aggregate_callback' => 'drupal_aggregate_css', ); $types['scripts'] = array( '#items' => array(), '#pre_render' => array('drupal_pre_render_scripts'), - '#group_callback' => 'drupal_group_js', - '#aggregate_callback' => 'drupal_aggregate_js', ); // Input elements. diff --git a/core/tests/Drupal/Tests/Core/Asset/CssCollectionRendererUnitTest.php b/core/tests/Drupal/Tests/Core/Asset/CssCollectionRendererUnitTest.php index 5bc9537..cd97a61 100644 --- a/core/tests/Drupal/Tests/Core/Asset/CssCollectionRendererUnitTest.php +++ b/core/tests/Drupal/Tests/Core/Asset/CssCollectionRendererUnitTest.php @@ -563,7 +563,7 @@ function testRenderInvalidType() { 'type' => 'internal', 'media' => 'all', 'preprocess' => TRUE, - 'browsers' => $browsers_default, + 'browsers' => array(), 'data' => 'http://example.com/popular.js' ); $this->renderer->render($css_group);