Commit ea568f1d authored by catch's avatar catch

Issue #784626 by Wim Leers, Manuel Garcia, jcisio, joelpittet, Bevan,...

Issue #784626 by Wim Leers, Manuel Garcia, jcisio, joelpittet, Bevan, fcaspani, dcmouyard: Default all JS to the footer, allow asset libraries to force their JS to the header
parent 5f7ec33a
......@@ -48,6 +48,7 @@ drupal:
misc/drupal.js: { weight: -18 }
dependencies:
- core/domready
- core/drupalSettings
drupalSettings:
version: VERSION
......@@ -308,6 +309,8 @@ drupal.vertical-tabs:
- core/drupal.form
html5shiv:
# Block the page from being loaded until html5shiv is initialized.
header: true
remote: https://github.com/aFarkas/html5shiv
version: 3.7.2
license:
......@@ -807,6 +810,8 @@ matchmedia.addListener:
- core/matchmedia
modernizr:
# Block the page from being loaded until Modernizr is initialized.
header: true
remote: https://github.com/Modernizr/Modernizr
license:
name: MIT
......@@ -814,7 +819,7 @@ modernizr:
gpl-compatible: true
version: v2.8.3
js:
assets/vendor/modernizr/modernizr.min.js: { every_page: true, preprocess: 0, scope: header, weight: -21, minified: true }
assets/vendor/modernizr/modernizr.min.js: { every_page: true, preprocess: 0, weight: -21, minified: true }
normalize:
remote: https://github.com/necolas/normalize.css
......
......@@ -135,8 +135,15 @@ protected function ajaxRender(Request $request) {
// Render the HTML to load these files, and add AJAX commands to insert this
// HTML in the page. Settings are handled separately, afterwards.
$settings = (isset($js_assets_header['drupalSettings'])) ? $js_assets_header['drupalSettings']['data'] : [];
unset($js_assets_header['drupalSettings']);
$settings = [];
if (isset($js_assets_header['drupalSettings'])) {
$settings = $js_assets_header['drupalSettings']['data'];
unset($js_assets_header['drupalSettings']);
}
if (isset($js_assets_footer['drupalSettings'])) {
$settings = $js_assets_footer['drupalSettings']['data'];
unset($js_assets_footer['drupalSettings']);
}
// Prepend commands to add the assets, preserving their relative order.
$resource_commands = array();
......
......@@ -214,8 +214,23 @@ protected function getJsSettingsAssets(AttachedAssetsInterface $assets) {
*/
public function getJsAssets(AttachedAssetsInterface $assets, $optimize) {
$javascript = [];
$libraries_to_load = $this->getLibrariesToLoad($assets);
foreach ($this->getLibrariesToLoad($assets) as $library) {
// Collect all libraries that contain JS assets and are in the header.
$header_js_libraries = [];
foreach ($libraries_to_load as $library) {
list($extension, $name) = explode('/', $library, 2);
$definition = $this->libraryDiscovery->getLibraryByName($extension, $name);
if (isset($definition['js']) && !empty($definition['header'])) {
$header_js_libraries[] = $library;
}
}
// The current list of header JS libraries are only those libraries that are
// in the header, but their dependencies must also be loaded for them to
// function correctly, so update the list with those.
$header_js_libraries = $this->libraryDependencyResolver->getLibrariesWithDependencies($header_js_libraries);
foreach ($libraries_to_load as $library) {
list($extension, $name) = explode('/', $library, 2);
$definition = $this->libraryDiscovery->getLibraryByName($extension, $name);
if (isset($definition['js'])) {
......@@ -225,7 +240,6 @@ public function getJsAssets(AttachedAssetsInterface $assets, $optimize) {
'group' => JS_DEFAULT,
'every_page' => FALSE,
'weight' => 0,
'scope' => 'header',
'cache' => TRUE,
'preprocess' => TRUE,
'attributes' => array(),
......@@ -233,6 +247,10 @@ public function getJsAssets(AttachedAssetsInterface $assets, $optimize) {
'browsers' => array(),
);
// 'scope' is a calculated option, based on which libraries are marked
// to be loaded from the header (see above).
$options['scope'] = in_array($library, $header_js_libraries) ? 'header' : 'footer';
// Preprocess can only be set if caching is enabled and no attributes
// are set.
$options['preprocess'] = $options['cache'] && empty($options['attributes']) ? $options['preprocess'] : FALSE;
......@@ -267,8 +285,6 @@ public function getJsAssets(AttachedAssetsInterface $assets, $optimize) {
}
}
// @todo Refactor this when the default scope is changed to 'footer' in
// https://www.drupal.org/node/784626
// If the core/drupalSettings library is being loaded or is already loaded,
// get the JavaScript settings assets, and convert them into a single
// "regular" JavaScript asset.
......@@ -278,7 +294,6 @@ public function getJsAssets(AttachedAssetsInterface $assets, $optimize) {
if ($settings_needed && $settings_have_changed) {
$settings = $this->getJsSettingsAssets($assets);
if (!empty($settings)) {
// Prepend to the list of JavaScript assets, to render it first.
$settings_as_inline_javascript = [
'type' => 'setting',
'group' => JS_SETTING,
......@@ -287,7 +302,15 @@ public function getJsAssets(AttachedAssetsInterface $assets, $optimize) {
'browsers' => array(),
'data' => $settings,
];
$js_assets_header = ['drupalSettings' => $settings_as_inline_javascript] + $js_assets_header;
$settings_js_asset = ['drupalSettings' => $settings_as_inline_javascript];
// Prepend to the list of JS assets, to render it first. Preferably in
// the footer, but in the header if necessary.
if (in_array('core/drupalSettings', $header_js_libraries)) {
$js_assets_header = $settings_js_asset + $js_assets_header;
}
else {
$js_assets_footer = $settings_js_asset + $js_assets_footer;
}
}
}
......
......@@ -86,6 +86,10 @@ public function buildByExtension($extension) {
}
$library += array('dependencies' => array(), 'js' => array(), 'css' => array());
if (isset($library['header']) && !is_bool($library['header'])) {
throw new \LogicException(sprintf("The 'header' key in the library definition '%s' in extension '%s' is invalid: it must be a boolean.", $id, $extension));
}
if (isset($library['version'])) {
// @todo Retrieve version of a non-core extension.
if ($library['version'] === 'VERSION') {
......
......@@ -34,7 +34,7 @@ drupal.editor.dialog:
quickedit.inPlaceEditor.formattedText:
version: VERSION
js:
js/editor.formattedTextEditor.js: { scope: footer, attributes: { defer: true } }
js/editor.formattedTextEditor.js: { attributes: { defer: true } }
dependencies:
- quickedit/quickedit
- editor/drupal.editor
......
......@@ -11,6 +11,6 @@ api:
mark-as-read:
version: VERSION
js:
js/mark-as-read.js: { scope: footer }
js/mark-as-read.js: {}
dependencies:
- history/api
......@@ -33,7 +33,7 @@ class LocaleLibraryAlterTest extends WebTestBase {
public function testLibraryAlter() {
$assets = new AttachedAssets();
$assets->setLibraries(['core/jquery.ui.datepicker']);
$js_assets = $this->container->get('asset.resolver')->getJsAssets($assets, FALSE)[0];
$js_assets = $this->container->get('asset.resolver')->getJsAssets($assets, FALSE)[1];
$this->assertTrue(array_key_exists('core/modules/locale/locale.datepicker.js', $js_assets), 'locale.datepicker.js added to scripts.');
}
}
......@@ -2,24 +2,24 @@ quickedit:
version: VERSION
js:
# Core.
js/quickedit.js: { scope: footer }
js/util.js: { scope: footer }
js/quickedit.js: {}
js/util.js: {}
# Models.
js/models/BaseModel.js: { scope: footer }
js/models/AppModel.js: { scope: footer }
js/models/EntityModel.js: { scope: footer }
js/models/FieldModel.js: { scope: footer }
js/models/EditorModel.js: { scope: footer }
js/models/BaseModel.js: {}
js/models/AppModel.js: {}
js/models/EntityModel.js: {}
js/models/FieldModel.js: {}
js/models/EditorModel.js: {}
# Views.
js/views/AppView.js: { scope: footer }
js/views/FieldDecorationView.js: { scope: footer }
js/views/EntityDecorationView.js: { scope: footer }
js/views/EntityToolbarView.js: { scope: footer }
js/views/ContextualLinkView.js: { scope: footer }
js/views/FieldToolbarView.js: { scope: footer }
js/views/EditorView.js: { scope: footer }
js/views/AppView.js: {}
js/views/FieldDecorationView.js: {}
js/views/EntityDecorationView.js: {}
js/views/EntityToolbarView.js: {}
js/views/ContextualLinkView.js: {}
js/views/FieldToolbarView.js: {}
js/views/EditorView.js: {}
# Other.
js/theme.js: { scope: footer }
js/theme.js: {}
css:
component:
css/quickedit.module.css: {}
......@@ -44,13 +44,13 @@ quickedit:
quickedit.inPlaceEditor.form:
version: VERSION
js:
js/editors/formEditor.js: { scope: footer }
js/editors/formEditor.js: {}
dependencies:
- quickedit/quickedit
quickedit.inPlaceEditor.plainText:
version: VERSION
js:
js/editors/plainTextEditor.js: { scope: footer }
js/editors/plainTextEditor.js: {}
dependencies:
- quickedit/quickedit
drupal.statistics:
version: VERSION
js:
statistics.js: { scope: footer }
statistics.js: {}
dependencies:
- core/jquery
- core/drupal
......
......@@ -47,7 +47,8 @@ public function testOrder() {
$assets = AttachedAssets::createFromRenderArray($build);
$css_render_array = $css_collection_renderer->render($asset_resolver->getCssAssets($assets, FALSE));
$expected_commands[1] = new AddCssCommand($renderer->render($css_render_array));
$build['#attached']['library'][] = 'ajax_test/order-js-command';
$build['#attached']['library'][] = 'ajax_test/order-header-js-command';
$build['#attached']['library'][] = 'ajax_test/order-footer-js-command';
$assets = AttachedAssets::createFromRenderArray($build);
list($js_assets_header, $js_assets_footer) = $asset_resolver->getJsAssets($assets, FALSE);
$js_header_render_array = $js_collection_renderer->render($js_assets_header);
......@@ -126,7 +127,7 @@ public function testLazyLoad() {
$assets->setLibraries([$expected['library_2']])
->setAlreadyLoadedLibraries($original_libraries);
$js_assets = $asset_resolver->getJsAssets($assets, FALSE)[0];
$js_assets = $asset_resolver->getJsAssets($assets, FALSE)[1];
unset($js_assets['drupalSettings']);
$js_render_array = $js_collection_renderer->render($js_assets);
$expected_js_html = $renderer->render($js_render_array);
......
......@@ -86,7 +86,7 @@ function testAddFiles() {
$assets = AttachedAssets::createFromRenderArray($build);
$css = $this->assetResolver->getCssAssets($assets, FALSE);
$js = $this->assetResolver->getJsAssets($assets, FALSE)[0];
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
$this->assertTrue(array_key_exists('bar.css', $css), 'CSS files are correctly added.');
$this->assertTrue(array_key_exists('core/modules/system/tests/modules/common_test/foo.js', $js), 'JavaScript files are correctly added.');
......@@ -107,11 +107,11 @@ function testAddJsSettings() {
$build['#attached']['library'][] = 'core/drupalSettings';
$assets = AttachedAssets::createFromRenderArray($build);
$javascript = $this->assetResolver->getJsAssets($assets, FALSE)[0];
$javascript = $this->assetResolver->getJsAssets($assets, FALSE)[1];
$this->assertTrue(array_key_exists('currentPath', $javascript['drupalSettings']['data']['path']), 'The current path JavaScript setting is set correctly.');
$assets->setSettings(['drupal' => 'rocks', 'dries' => 280342800]);
$javascript = $this->assetResolver->getJsAssets($assets, FALSE)[0];
$javascript = $this->assetResolver->getJsAssets($assets, FALSE)[1];
$this->assertEqual(280342800, $javascript['drupalSettings']['data']['dries'], 'JavaScript setting is set correctly.');
$this->assertEqual('rocks', $javascript['drupalSettings']['data']['drupal'], 'The other JavaScript setting is set correctly.');
}
......@@ -124,7 +124,7 @@ function testAddExternalFiles() {
$assets = AttachedAssets::createFromRenderArray($build);
$css = $this->assetResolver->getCssAssets($assets, FALSE);
$js = $this->assetResolver->getJsAssets($assets, FALSE)[0];
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
$this->assertTrue(array_key_exists('http://example.com/stylesheet.css', $css), 'External CSS files are correctly added.');
$this->assertTrue(array_key_exists('http://example.com/script.js', $js), 'External JavaScript files are correctly added.');
......@@ -143,7 +143,7 @@ function testAttributes() {
$build['#attached']['library'][] = 'common_test/js-attributes';
$assets = AttachedAssets::createFromRenderArray($build);
$js = $this->assetResolver->getJsAssets($assets, FALSE)[0];
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
$rendered_js = $this->renderer->render($js_render_array);
$expected_1 = '<script src="http://example.com/deferred-external.js" foo="bar" defer></script>';
......@@ -159,7 +159,7 @@ function testAggregatedAttributes() {
$build['#attached']['library'][] = 'common_test/js-attributes';
$assets = AttachedAssets::createFromRenderArray($build);
$js = $this->assetResolver->getJsAssets($assets, TRUE)[0];
$js = $this->assetResolver->getJsAssets($assets, TRUE)[1];
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
$rendered_js = $this->renderer->render($js_render_array);
$expected_1 = '<script src="http://example.com/deferred-external.js" foo="bar" defer></script>';
......@@ -169,16 +169,16 @@ function testAggregatedAttributes() {
}
/**
* Tests drupal_get_js() for JavaScript settings.
* Tests JavaScript settings.
*/
function testHeaderSetting() {
function testSettings() {
$build = array();
$build['#attached']['library'][] = 'core/drupalSettings';
// Nonsensical value to verify if it's possible to override path settings.
$build['#attached']['drupalSettings']['path']['pathPrefix'] = 'yarhar';
$assets = AttachedAssets::createFromRenderArray($build);
$js = $this->assetResolver->getJsAssets($assets, FALSE)[0];
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
$rendered_js = $this->renderer->render($js_render_array);
......@@ -207,17 +207,19 @@ function testHeaderSetting() {
}
/**
* Tests JS assets assigned to the 'footer' scope.
* Tests JS assets depending on the 'core/<head>' virtual library.
*/
function testFooterHTML() {
$build['#attached']['library'][] = 'common_test/js-footer';
function testHeaderHTML() {
$build['#attached']['library'][] = 'common_test/js-header';
$assets = AttachedAssets::createFromRenderArray($build);
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
$js = $this->assetResolver->getJsAssets($assets, FALSE)[0];
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
$rendered_js = $this->renderer->render($js_render_array);
$query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0';
$this->assertNotIdentical(strpos($rendered_js, '<script src="' . file_create_url('core/modules/system/tests/modules/common_test/footer.js') . '?' . $query_string . '"></script>'), FALSE, 'Rendering an external JavaScript file.');
$this->assertNotIdentical(strpos($rendered_js, '<script src="' . file_create_url('core/modules/system/tests/modules/common_test/header.js') . '?' . $query_string . '"></script>'), FALSE, 'The JS asset in common_test/js-header appears in the header.');
$this->assertNotIdentical(strpos($rendered_js, '<script src="' . file_create_url('core/misc/drupal.js')), FALSE, 'The JS asset of the direct dependency (core/drupal) of common_test/js-header appears in the header.');
$this->assertNotIdentical(strpos($rendered_js, '<script src="' . file_create_url('core/assets/vendor/domready/ready.min.js')), FALSE, 'The JS asset of the indirect dependency (core/domready) of common_test/js-header appears in the header.');
}
/**
......@@ -227,7 +229,7 @@ function testNoCache() {
$build['#attached']['library'][] = 'common_test/no-cache';
$assets = AttachedAssets::createFromRenderArray($build);
$js = $this->assetResolver->getJsAssets($assets, FALSE)[0];
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
$this->assertFalse($js['core/modules/system/tests/modules/common_test/nocache.js']['preprocess'], 'Setting cache to FALSE sets preprocess to FALSE when adding JavaScript.');
}
......@@ -242,7 +244,7 @@ function testBrowserConditionalComments() {
$build['#attached']['library'][] = 'common_test/browsers';
$assets = AttachedAssets::createFromRenderArray($build);
$js = $this->assetResolver->getJsAssets($assets, FALSE)[0];
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
$rendered_js = $this->renderer->render($js_render_array);
$expected_1 = "<!--[if lte IE 8]>\n" . '<script src="' . file_create_url('core/modules/system/tests/modules/common_test/old-ie.js') . '?' . $default_query_string . '"></script>' . "\n<![endif]-->";
......@@ -260,7 +262,7 @@ function testVersionQueryString() {
$build['#attached']['library'][] = 'core/domready';
$assets = AttachedAssets::createFromRenderArray($build);
$js = $this->assetResolver->getJsAssets($assets, FALSE)[0];
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
$rendered_js = $this->renderer->render($js_render_array);
$this->assertTrue(strpos($rendered_js, 'core/assets/vendor/backbone/backbone-min.js?v=1.1.2') > 0 && strpos($rendered_js, 'core/assets/vendor/domready/ready.min.js?v=1.0.7') > 0 , 'JavaScript version identifiers correctly appended to URLs');
......@@ -288,7 +290,7 @@ function testRenderOrder() {
];
// Retrieve the rendered JavaScript and test against the regex.
$js = $this->assetResolver->getJsAssets($assets, FALSE)[0];
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
$rendered_js = $this->renderer->render($js_render_array);
$matches = array();
......@@ -353,7 +355,7 @@ function testRenderDifferentWeight() {
$build['#attached']['library'][] = 'common_test/weight';
$assets = AttachedAssets::createFromRenderArray($build);
$js = $this->assetResolver->getJsAssets($assets, FALSE)[0];
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
$rendered_js = $this->renderer->render($js_render_array);
$this->assertTrue(strpos($rendered_js, 'lighter.css') < strpos($rendered_js, 'first.js'), 'Lighter CSS assets are rendered first.');
......@@ -375,7 +377,7 @@ function testAlter() {
// Render the JavaScript, testing if simpletest.js was altered to be before
// tableselect.js. See simpletest_js_alter() to see where this alteration
// takes place.
$js = $this->assetResolver->getJsAssets($assets, FALSE)[0];
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
$rendered_js = $this->renderer->render($js_render_array);
$this->assertTrue(strpos($rendered_js, 'simpletest.js') < strpos($rendered_js, 'core/misc/tableselect.js'), 'Altering JavaScript weight through the alter hook.');
......@@ -396,7 +398,7 @@ function testLibraryAlter() {
// common_test_library_info_alter() also added a dependency on jQuery Form.
$build['#attached']['library'][] = 'core/jquery.farbtastic';
$assets = AttachedAssets::createFromRenderArray($build);
$js = $this->assetResolver->getJsAssets($assets, FALSE)[0];
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
$rendered_js = $this->renderer->render($js_render_array);
$this->assertTrue(strpos($rendered_js, 'core/assets/vendor/jquery-form/jquery.form.js'), 'Altered library dependencies are added to the page.');
......@@ -444,7 +446,7 @@ function testAddJsFileWithQueryString() {
$assets = AttachedAssets::createFromRenderArray($build);
$css = $this->assetResolver->getCssAssets($assets, FALSE);
$js = $this->assetResolver->getJsAssets($assets, FALSE)[0];
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
$this->assertTrue(array_key_exists('querystring.css?arg1=value1&arg2=value2', $css), 'CSS file with query string is correctly added.');
$this->assertTrue(array_key_exists('core/modules/system/tests/modules/common_test/querystring.js?arg1=value1&arg2=value2', $js), 'JavaScript file with query string is correctly added.');
......
......@@ -3,7 +3,8 @@ order:
ajax: test
dependencies:
- ajax_test/order-css-command
- ajax_test/order-js-command
- ajax_test/order-footer-js-command
- ajax_test/order-header-js-command
order-css-command:
css:
......@@ -12,9 +13,11 @@ order-css-command:
a.css: {}
b.css: {}
order-js-command:
order-footer-js-command:
js:
# Two JavaScript files (first to the footer, should appear last).
footer.js: { scope: footer }
header.js: {}
footer.js: {}
order-header-js-command:
header: true
js:
header.js: {}
......@@ -36,9 +36,12 @@ js-attributes:
foo: bar
defer: true
js-footer:
js-header:
header: true
js:
footer.js: { scope: footer }
header.js: {}
dependencies:
- core/drupal
# Library to test setting cache = FALSE, to prevent aggregation.
no-cache:
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment