wimleers.com
@wimleers
Senior Software Engineer
Office of the CTO, Acquia
Not anymore :)
tableselect.js
(function ($, Drupal) {
"use strict";
Drupal.behaviors.tableSelect = {
attach: function (context, settings) {
$(context).find('th.select-all').closest('table') …
}
};
…
})(jQuery, Drupal);
jQuery
is usedDrupal
is used⟹ put them in the closure
$libraries['drupal.tableselect'] = array(
'title' => 'Tableselect',
…
'js' => array(
'core/misc/tableselect.js' => array(),
),
'dependencies' => array(
array('system', 'jquery'),
array('system', 'drupal'),
),
);
jQuery
and Drupal
in the closure ⟹ dependencies
drupal_add_(css|js)()
// Add a "Select all" checkbox.
- drupal_add_js('core/misc/tableselect.js');
+ drupal_add_library('system', 'drupal.tableselect');
drupal_add_*()
Always use #attached
.
// Add a "Select all" checkbox.
- drupal_add_library('system', 'drupal.tableselect');
+ $element['#attached']['library'][] = array('system', 'drupal.tableselect');
drupal_add_*()
uses global state ⟹ breaks caching
Load assets by #attach
ing libraries
Almost!
… we had to fix a thing or two:
… but still not yet there!
“There are only two hard problems in Computer Science: cache invalidation and naming things.”
personalization
… clears the entire page cache on node save!
cache_clear_all('foo:content:id', $bin);
cache_clear_all('foo:content:', $bin, TRUE);
cache_clear_all('*', $bin, TRUE);
⬇
“How to clear all entries containing node 42?”
⬇
Impossible!
cache($bin)->set($cid, $value, CACHE_PERMANENT, $tags);
$tags = array('content' => TRUE);
$tags = array(
'node' => array(42)
);
$tags = array(
'node' => array(42),
'user' => array(314),
'taxonomy_term' => array(1337, 9001),
);
$element['#cache'] = array(
'keys' => array('entity_view', $entity_type, $entity_id, $view_mode),
'granularity' => DRUPAL_CACHE_PER_ROLE,
'tags' => array(
$entity_type . '_view' => TRUE,
$entity_type => array($entity_id),
),
);
+
/**
* Collects cache tags for an element and its children into 1 array.
*
* […] This allows items to be invalidated based on all tags attached
* to the content they're constituted from.
*/
function drupal_render_collect_cache_tags($element) { … }
Clear caches:
deleteTags()
: prevents stale contentinvalidateTags()
: allows stale content
⟹ breaks render cache
or
⟹ compatible with render cache
Contextual IDs in HTML
+
contextual.js
(If Use contextual links permission.)
+
POST contextual IDs to contextual/render
=
Same HTML for all users!
(P15n applied later)
- {% if new %}
- {{ new }}
- {% endif %}
+
/**
* Implements hook_node_view_alter().
*/
function comment_node_view_alter(&$build, EntityInterface $node, EntityDisplay $display) {
if (module_exists('history')) {
$build['#attributes']['data-history-node-id'] = $node->id();
}
}
⟹ compatible with render cache
…
…
+
comment-new-indicator.js
(If authenticated)
+
POST node IDs to history/get_node_read_timestamps
=
Same HTML for all users!
(P15n applied later)
Similar.
data-
attributes with "universal truths"Pioneered by in-place editing, now applied in many places.
Use cache tags precisely (cache invalidation)
Personalize via JS, cache client-side (cache filling)
data-
attributeslocalStorage
Separate requests for:
⬇
1 → 5 reqs/page load!?
p15n: server ⟶ client
slower | ⟺ | cold cache | ⇒ | 5 reqs |
faster | ⟺ | warm cache | ⇒ | 1 req |
“HTML skeleton + JS p15n” is faster
⇕
Warm client-side cache
&
More p15n ⟹ slower!
The question:
How to keep the cache warm?
“There are only two hard problems in Computer Science: cache invalidation and naming things.”
In hook_page_build():
$element['#attached']['js'][] = array(
'type' => 'setting',
'data' => array('toolbar' => array(
'subtreesHash' => _toolbar_get_subtrees_hash(),
)),
);
Hash depends on:
var LS = localStorage;
function load() {
var subtreesHash = drupalSettings.toolbar.subtreesHash;
var cachedSubtreesHash = LS.getItem('Drupal.toolbar.subtreesHash');
// Identical hashes?
if (subtreesHash === cachedSubtreesHash) {
var subtrees = JSON.parse(LS.getItem('Drupal.toolbar.subtrees'));
Drupal.toolbar.setSubtrees.resolve(subtrees);
}
else {
LS.removeItem('Drupal.toolbar.subtrees');
$.ajax(Drupal.url('toolbar/subtrees/' + subtreesHash));
// Remember the new hash.
LS.setItem('Drupal.toolbar.subtreesHash', subtreesHash);
}
}
function save() {
LS.setItem('Drupal.toolbar.subtrees', JSON.stringify(subtrees));
}
localStorage
⬇
No hash necessary!
… we would have many hashes
(~ client-side cache tags)
drupalSettings.user.uid
(Used by "new" marker.)
drupalSettings.user.permissionsHash
(Used by contextual links, in-place editing.)
Cache slowly changing data
Keep the cache warm to keep it fast!
drupalSettings
)“API”:
#aggregate_callback
#group_callback
⬇
Override the whole, or nothing … or hacks
⬇
Incompatibility hell: Omega theme, CDN module …
API:
Identical logic, but restructured
(See d.o/node/2034675)
AssetCollectionOptimizerInterface
AssetCollectionGrouperInterface
AssetOptimizerInterface
AssetDumperInterface
AssetCollectionRendererInterface
Override at will!
asset.css.collection_renderer:
class: Drupal\Core\Asset\CssCollectionRenderer
asset.css.collection_optimizer:
class: Drupal\Core\Asset\CssCollectionOptimizer
arguments: [ '@asset.css.collection_grouper', '@asset.css.optimizer', '@asset.css.dumper', '@state' ]
asset.css.optimizer:
class: Drupal\Core\Asset\CssOptimizer
asset.css.collection_grouper:
class: Drupal\Core\Asset\CssCollectionGrouper
asset.css.dumper:
class: Drupal\Core\Asset\AssetDumper
asset.js.collection_renderer:
…
asset.js.collection_optimizer:
…
asset.js.optimizer:
…
asset.js.collection_grouper:
…
asset.js.dumper:
…
mtime
Override individual asset services
We need your help!
[#1605290] | Entity render caching (with cache tag support) |
[#1712456] | Views + cache tags |
[#678292] | Enable CSS & JS aggregation by default |
[#2005644] | Permissions hash: remove 2 HTTP reqs/page! |
… |
… anything tagged D8 cacheability!