diff --git a/core/includes/common.inc b/core/includes/common.inc index 43c5384..ce4a93b 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -581,7 +581,8 @@ function drupal_js_defaults($data = NULL) { */ function drupal_process_attached(array $elements) { // Asset attachments are handled by \Drupal\Core\Asset\AssetResolver. - foreach (array('library', 'drupalSettings') as $type) { + // @todo This needs to be configurable somehow. + foreach (array('library', 'drupalSettings', 'big_pipe_placeholders') as $type) { unset($elements['#attached'][$type]); } diff --git a/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php b/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php index 7d36b51..80bc7f9 100644 --- a/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php +++ b/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php @@ -105,16 +105,19 @@ public function __construct(AssetResolverInterface $asset_resolver, ConfigFactor /** * {@inheritdoc} */ - public function processAttachments(AttachmentsInterface $response) { + public function processAttachments(AttachmentsInterface $response, $ajax_page_state = NULL) { // @todo Convert to assertion once https://www.drupal.org/node/2408013 lands if (!$response instanceof AjaxResponse) { throw new \InvalidArgumentException('\Drupal\Core\Ajax\AjaxResponse instance expected.'); } - $request = $this->requestStack->getCurrentRequest(); - if ($response->getContent() == '{}') { - $response->setData($this->buildAttachmentsCommands($response, $request)); + if (!isset($ajax_page_state)) { + $request = $this->requestStack->getCurrentRequest(); + $ajax_page_state = $request->request->get('ajax_page_state'); + } + + $response->setData($this->buildAttachmentsCommands($response, $ajax_page_state)); } return $response; @@ -125,15 +128,13 @@ public function processAttachments(AttachmentsInterface $response) { * * @param \Drupal\Core\Ajax\AjaxResponse $response * The AJAX response to update. - * @param \Symfony\Component\HttpFoundation\Request $request - * The request object that the AJAX is responding to. + * @param array $ajax_page_state + * The current ajax page state. * * @return array * An array of commands ready to be returned as JSON. */ - protected function buildAttachmentsCommands(AjaxResponse $response, Request $request) { - $ajax_page_state = $request->request->get('ajax_page_state'); - + protected function buildAttachmentsCommands(AjaxResponse $response, array $ajax_page_state = NULL) { // Aggregate CSS/JS if necessary, but only during normal site operation. $optimize_css = !defined('MAINTENANCE_MODE') && $this->config->get('css.preprocess'); $optimize_js = !defined('MAINTENANCE_MODE') && $this->config->get('js.preprocess'); diff --git a/core/lib/Drupal/Core/Asset/LibraryDependencyResolver.php b/core/lib/Drupal/Core/Asset/LibraryDependencyResolver.php index 34ffc0d..f05fbbd 100644 --- a/core/lib/Drupal/Core/Asset/LibraryDependencyResolver.php +++ b/core/lib/Drupal/Core/Asset/LibraryDependencyResolver.php @@ -94,7 +94,7 @@ public function getMinimalRepresentativeSubset(array $libraries) { } } - return $minimal; + return array_unique($minimal); } } diff --git a/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php b/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php index 19edef1..eb13b11 100644 --- a/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php +++ b/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php @@ -123,7 +123,13 @@ public function processAttachments(AttachmentsInterface $response) { $attachment_placeholders = $attached['html_response_attachment_placeholders']; unset($attached['html_response_attachment_placeholders']); - $variables = $this->processAssetLibraries($attached, $attachment_placeholders); + // Take Ajax page state into account, to allow for something like Turbolinks + // to be implemented without altering core. + // @see https://github.com/rails/turbolinks/ + // @todo https://www.drupal.org/node/2497115 - Below line is broken due to ->request. + $ajax_page_state = $this->requestStack->getCurrentRequest()->request->get('ajax_page_state'); + + $variables = $this->processAssetLibraries($attached, $attachment_placeholders, $ajax_page_state); // Handle all non-asset attachments. This populates drupal_get_html_head(). $all_attached = ['#attached' => $attached]; @@ -201,6 +207,8 @@ protected function renderPlaceholders(HtmlResponse $response) { * The attachments to process. * @param array $placeholders * The placeholders that exist in the response. + * @param array $ajax_page_state + * (optional) The ajax page state of the page. * * @return array * An array keyed by asset type, with keys: @@ -208,15 +216,10 @@ protected function renderPlaceholders(HtmlResponse $response) { * - scripts * - scripts_bottom */ - protected function processAssetLibraries(array $attached, array $placeholders) { + public function processAssetLibraries(array $attached, array $placeholders, array $ajax_page_state = NULL) { $all_attached = ['#attached' => $attached]; $assets = AttachedAssets::createFromRenderArray($all_attached); - // Take Ajax page state into account, to allow for something like Turbolinks - // to be implemented without altering core. - // @see https://github.com/rails/turbolinks/ - // @todo https://www.drupal.org/node/2497115 - Below line is broken due to ->request. - $ajax_page_state = $this->requestStack->getCurrentRequest()->request->get('ajax_page_state'); $assets->setAlreadyLoadedLibraries(isset($ajax_page_state) ? explode(',', $ajax_page_state['libraries']) : []); $variables = []; @@ -233,7 +236,13 @@ protected function processAssetLibraries(array $attached, array $placeholders) { // Optimize JS if necessary, but only during normal site operation. $optimize_js = !defined('MAINTENANCE_MODE') && !\Drupal::state()->get('system.maintenance_mode') && $this->config->get('js.preprocess'); list($js_assets_header, $js_assets_footer) = $this->assetResolver->getJsAssets($assets, $optimize_js); + } + + if (isset($placeholders['scripts'])) { $variables['scripts'] = $this->jsCollectionRenderer->render($js_assets_header); + } + + if (isset($placeholders['scripts_bottom'])) { $variables['scripts_bottom'] = $this->jsCollectionRenderer->render($js_assets_footer); } diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php index 48b3bcd..5d48203 100644 --- a/core/lib/Drupal/Core/Render/Renderer.php +++ b/core/lib/Drupal/Core/Render/Renderer.php @@ -167,7 +167,7 @@ public function renderPlain(&$elements) { * * @todo Make public as part of https://www.drupal.org/node/2469431 */ - protected function renderPlaceholder($placeholder, array $elements) { + public function renderPlaceholder($placeholder, array $elements) { // Get the render array for the given placeholder $placeholder_elements = $elements['#attached']['placeholders'][$placeholder]; @@ -330,6 +330,7 @@ protected function doRender(&$elements, $is_root_call = FALSE) { '#lazy_builder', '#cache', '#create_placeholder', + '#create_placeholder_options', // These keys are not actually supported, but they are added automatically // by the Renderer, so we don't crash on them; them being missing when // their #lazy_builder callback is invoked won't surprise the developer. @@ -699,6 +700,8 @@ protected function createPlaceholder(array $element) { // The cacheability metadata for the placeholder. The rendered result of // the placeholder may itself be cached, if [#cache][keys] are specified. '#cache' => TRUE, + // The options for creating the placeholder. (optional) + '#create_placeholder_options' => TRUE, ]); // Generate placeholder markup. Note that the only requirement is that this diff --git a/core/modules/big_pipe/big_pipe.info.yml b/core/modules/big_pipe/big_pipe.info.yml new file mode 100644 index 0000000..b0f6e91 --- /dev/null +++ b/core/modules/big_pipe/big_pipe.info.yml @@ -0,0 +1,6 @@ +name: BigPipe +type: module +description: 'Enables BigPipe for authenticated users; first send+render the cheap parts of the page, then the expensive parts.' +package: Core +version: VERSION +core: 8.x diff --git a/core/modules/big_pipe/big_pipe.libraries.yml b/core/modules/big_pipe/big_pipe.libraries.yml new file mode 100644 index 0000000..88ecff6 --- /dev/null +++ b/core/modules/big_pipe/big_pipe.libraries.yml @@ -0,0 +1,11 @@ +big_pipe: + version: VERSION + js: + js/big_pipe.js: {} + drupalSettings: + bigPipePlaceholders: [] + dependencies: + - core/jquery + - core/drupal + - core/drupal.ajax + - core/drupalSettings diff --git a/core/modules/big_pipe/big_pipe.module b/core/modules/big_pipe/big_pipe.module new file mode 100644 index 0000000..724cd1c --- /dev/null +++ b/core/modules/big_pipe/big_pipe.module @@ -0,0 +1,66 @@ +setAjaxPageState(isset($settings['ajaxPageState']) ? $settings['ajaxPageState'] : NULL); + } +} + +/** + * Implements hook_form_FORM_ID_alter(). + */ +function big_pipe_form_user_login_form_alter(&$form, FormStateInterface $form_state, $form_id) { + // Check if the user has JavaScript enabled without adding JavaScript. + $form['big_pipe_has_js'] = array( + '#type' => 'hidden', + '#default_value' => '1', + ); + + $form['#after_build'][] = 'big_pipe_form_after_build'; + $form['#submit'][] = 'big_pipe_form_set_js_check'; +} + +/** + * After build handler for user_login_form(). + */ +function big_pipe_form_after_build($form, FormStateInterface $form_state) { + // This is tricky: We want Form API to default big_pipe_has_js to 1 in + // case it gets not send. We also want to set the value of the HTML element + // to 0 and add