drupal_add_js() already was idempotent for adding files. It was not idempotent for adding JavaScript settings, because there were several "special behaviors" for merging JavaScript settings, to simplify the lives of PHP developers a little bit, at the cost of having some impossible-to-solve problems.
When settings are associated with e.g. a text format, a block, a field formatter or anything else that could appear multiple times on the same page, it is important that adding the same setting twice doesn't break anything. In Drupal 7, many ensured that JavaScript settings were added only once per request by using some static $variable.
With the Drupal 8's (upcoming) new rendering pipeline (everything as blocks + subrequests), that trick can no longer be employed, because one must have all CSS, JavaScript, libraries and JavaScript settings needed by a block #attached to each block's render array. The point of blocks + subrequests is that it is possible to render the main page as if it were a skeleton with holes the main page didn't have to know anything about. Hence, we can't use a per-request static $variable anymore, and it's now each block's responsibility to know which assets are needed. Because two independently rendered blocks might be adding the same JavaScript settings, we've finally arrived at the point where we must guarantee idempotency.
Example
Idempotency for merging of scalar values
drupal_add_js(array('myModule', array('key' => 'value')), 'setting'); drupal_add_js(array('myModule', array('key' => 'value')), 'setting');the second call has no effect (as you would expect), and you get the following in
drupalSettings:
{myModule: {key: value}}Before, you would have gotten
{myModule: {key: [value, value]}}
Idempotency for merging of array values
drupal_add_js(array('myModule' => array('otherKey' => array('foo', 'bar'))), 'setting'); drupal_add_js(array('myModule' => array('otherKey' => array('foo', 'bar'))), 'setting');the second call has no effect (as you would expect), and you get the following in
drupalSettings:
{myModule: {otherKey: ['foo', 'bar']}}Before, you would have gotten
{myModule: {otherKey: ['foo', 'bar', 'foo', 'bar']}}
Just like on the front-end
This means that the behavior of merging attached JavaScript settings (which are merged by Renderer::mergeAttachments()) now behaves in exactly the same way as
jQuery.extend(true, {}, $settings_items[0], $settings_items[1], …)
would. This means integer indices are preserved just like string indices are, rather than re-indexed as is common in PHP array merging.
Consequences
You're no longer able to enhance a list (array) of items with additional items now, because the implementation assumes that there are no non-indexed arrays at all anymore. In other words: integer keys are now merged.
In Drupal 7, you could do something like this:
persister.js:
drupalSettings.persister.IDsToPersist = [];foo.module:
drupal_add_js(array('myModule' => array('persister' => array('IDsToPersist' => array('foo-bar', 'foo-baz')))), 'setting');bar.module:
drupal_add_js(array('myModule' => array('persister' => array('IDsToPersist' => array('bar-foo', 'bar-baz')))), 'setting');}Then you would get:
drupalSettings.persister.IDsToPersist = ['foo-bar', 'foo-baz', 'bar-foo', 'bar-baz'];
But in Drupal 8, to be able to continue to use the exact same JS code, you would have to do something like this:
foo.module:
drupal_add_js(array('myModule' => array('persister' => array('IDsToPersist' => array('foo' => array('foo-bar', 'foo-baz'))))), 'setting');bar.module:
drupal_add_js(array('myModule' => array('persister' => array('IDsToPersist' => array('bar' => array('bar-foo', 'bar-baz'))))), 'setting');Then you would get:
drupalSettings.persister.IDsToPersist = {foo: ['foo-bar', 'foo-baz'], bar: ['bar-foo', 'bar-baz']}; drupalSettings.persister.IDsToPersist = mergeKeyedArrays(drupalSettings.persister.IDsToPersist); drupalSettings.persister.IDsToPersist = ['foo-bar', 'foo-baz', 'bar-foo', 'bar-baz'];with:
function mergeKeyedArrays(keyedArrays) { var mergedArray = []; for (key in keyedArrays) { if (keyedArrays.hasOwnProperty(key)) { mergedArray = mergedArray.concat(keyedArrays[key]); } } return mergedArray; }
In Drupal 8, this becomes vitally important, with the move towards ESI/parallelized rendering, because there it will become effectively impossible to have "global/page-level JS settings", because each part will be rendered individually. Hence this problem will be noticed by many more developers.