From 8287017e034bc323dec1d86b3f37a804aa082d2d Mon Sep 17 00:00:00 2001
From: Alex Pott <alex.a.pott@googlemail.com>
Date: Fri, 19 May 2017 23:12:53 +0100
Subject: [PATCH] Issue #2818825 by drpal, nod_, droplet, cilefen: Rename all
 JS files to *.es6.js and compile them

---
 core/misc/active-link.es6.js                  |   68 +
 core/misc/active-link.js                      |   51 +-
 core/misc/ajax.es6.js                         | 1344 ++++++++++++++
 core/misc/ajax.js                             |  873 +--------
 core/misc/announce.es6.js                     |  120 ++
 core/misc/announce.js                         |   85 +-
 core/misc/autocomplete.es6.js                 |  288 +++
 core/misc/autocomplete.js                     |  175 +-
 core/misc/batch.es6.js                        |   46 +
 core/misc/batch.js                            |   24 +-
 core/misc/collapse.es6.js                     |  146 ++
 core/misc/collapse.js                         |  107 +-
 core/misc/date.es6.js                         |   56 +
 core/misc/date.js                             |   40 +-
 core/misc/debounce.es6.js                     |   52 +
 core/misc/debounce.js                         |   36 +-
 core/misc/details-aria.es6.js                 |   29 +
 core/misc/details-aria.js                     |   19 +-
 core/misc/dialog/dialog.ajax.es6.js           |  246 +++
 core/misc/dialog/dialog.ajax.js               |  139 +-
 core/misc/dialog/dialog.es6.js                |  100 ++
 core/misc/dialog/dialog.jquery-ui.es6.js      |   36 +
 core/misc/dialog/dialog.jquery-ui.js          |   14 +-
 core/misc/dialog/dialog.js                    |   70 +-
 core/misc/dialog/dialog.position.es6.js       |  112 ++
 core/misc/dialog/dialog.position.js           |   71 +-
 core/misc/displace.es6.js                     |  222 +++
 core/misc/displace.js                         |  144 +-
 core/misc/dropbutton/dropbutton.es6.js        |  233 +++
 core/misc/dropbutton/dropbutton.js            |  189 +-
 core/misc/drupal.es6.js                       |  583 ++++++
 core/misc/drupal.init.es6.js                  |   19 +
 core/misc/drupal.init.js                      |   21 +-
 core/misc/drupal.js                           |  434 +----
 core/misc/drupalSettingsLoader.es6.js         |   25 +
 core/misc/drupalSettingsLoader.js             |   19 +-
 core/misc/entity-form.es6.js                  |   57 +
 core/misc/entity-form.js                      |   34 +-
 core/misc/form.es6.js                         |  250 +++
 core/misc/form.js                             |  175 +-
 core/misc/machine-name.es6.js                 |  211 +++
 core/misc/machine-name.js                     |  113 +-
 core/misc/progress.es6.js                     |  169 ++
 core/misc/progress.js                         |  118 +-
 core/misc/states.es6.js                       |  724 ++++++++
 core/misc/states.js                           |  474 +----
 core/misc/tabbingmanager.es6.js               |  369 ++++
 core/misc/tabbingmanager.js                   |  266 +--
 core/misc/tabledrag.es6.js                    | 1557 +++++++++++++++++
 core/misc/tabledrag.js                        |  865 ++-------
 core/misc/tableheader.es6.js                  |  316 ++++
 core/misc/tableheader.js                      |  201 +--
 core/misc/tableresponsive.es6.js              |  174 ++
 core/misc/tableresponsive.js                  |  157 +-
 core/misc/tableselect.es6.js                  |  159 ++
 core/misc/tableselect.js                      |   93 +-
 core/misc/timezone.es6.js                     |   76 +
 core/misc/timezone.js                         |   55 +-
 core/misc/vertical-tabs.es6.js                |  252 +++
 core/misc/vertical-tabs.js                    |  183 +-
 core/modules/big_pipe/js/big_pipe.es6.js      |  110 ++
 core/modules/big_pipe/js/big_pipe.js          |   64 +-
 core/modules/block/js/block.admin.es6.js      |   97 +
 core/modules/block/js/block.admin.js          |   61 +-
 core/modules/block/js/block.es6.js            |  228 +++
 core/modules/block/js/block.js                |  176 +-
 core/modules/book/book.es6.js                 |   37 +
 core/modules/book/book.js                     |   28 +-
 .../modules/ckeditor/js/ckeditor.admin.es6.js |  499 ++++++
 core/modules/ckeditor/js/ckeditor.admin.js    |  349 +---
 .../js/ckeditor.drupalimage.admin.es6.js      |   45 +
 .../ckeditor/js/ckeditor.drupalimage.admin.js |   26 +-
 core/modules/ckeditor/js/ckeditor.es6.js      |  350 ++++
 core/modules/ckeditor/js/ckeditor.js          |  178 +-
 .../js/ckeditor.language.admin.es6.js         |   16 +
 .../ckeditor/js/ckeditor.language.admin.js    |   16 +-
 .../js/ckeditor.stylescombo.admin.es6.js      |  128 ++
 .../ckeditor/js/ckeditor.stylescombo.admin.js |   96 +-
 core/modules/ckeditor/js/models/Model.es6.js  |   75 +
 core/modules/ckeditor/js/models/Model.js      |   61 +-
 .../js/plugins/drupalimage/plugin.es6.js      |  371 ++++
 .../ckeditor/js/plugins/drupalimage/plugin.js |  153 +-
 .../plugins/drupalimagecaption/plugin.es6.js  |  301 ++++
 .../js/plugins/drupalimagecaption/plugin.js   |  117 +-
 .../js/plugins/drupallink/plugin.es6.js       |  304 ++++
 .../ckeditor/js/plugins/drupallink/plugin.js  |  121 +-
 .../ckeditor/js/views/AuralView.es6.js        |  233 +++
 core/modules/ckeditor/js/views/AuralView.js   |  152 +-
 .../ckeditor/js/views/ControllerView.es6.js   |  383 ++++
 .../ckeditor/js/views/ControllerView.js       |  295 +---
 .../ckeditor/js/views/KeyboardView.es6.js     |  266 +++
 .../modules/ckeditor/js/views/KeyboardView.js |  249 +--
 .../ckeditor/js/views/VisualView.es6.js       |  273 +++
 core/modules/ckeditor/js/views/VisualView.js  |  182 +-
 .../ckeditor/tests/modules/js/ajax-css.es6.js |   24 +
 .../ckeditor/tests/modules/js/ajax-css.js     |   25 +-
 core/modules/color/color.es6.js               |  297 ++++
 core/modules/color/color.js                   |  208 +--
 core/modules/color/preview.es6.js             |   74 +
 core/modules/color/preview.js                 |   41 +-
 .../js/color_test_theme-fontsize.es6.js       |    9 +
 .../js/color_test_theme-fontsize.js           |   12 +-
 .../comment/comment-entity-form.es6.js        |   23 +
 core/modules/comment/comment-entity-form.js   |   18 +-
 .../comment/js/comment-by-viewer.es6.js       |   26 +
 core/modules/comment/js/comment-by-viewer.js  |   27 +-
 .../comment/js/comment-new-indicator.es6.js   |   96 +
 .../comment/js/comment-new-indicator.js       |   74 +-
 .../comment/js/node-new-comments-link.es6.js  |  177 ++
 .../comment/js/node-new-comments-link.js      |  140 +-
 .../content_translation.admin.es6.js          |  131 ++
 .../content_translation.admin.js              |   79 +-
 core/modules/contextual/js/contextual.es6.js  |  256 +++
 core/modules/contextual/js/contextual.js      |  153 +-
 .../contextual/js/contextual.toolbar.es6.js   |   77 +
 .../contextual/js/contextual.toolbar.js       |   42 +-
 .../contextual/js/models/StateModel.es6.js    |  132 ++
 .../contextual/js/models/StateModel.js        |   99 +-
 .../js/toolbar/models/StateModel.es6.js       |  119 ++
 .../js/toolbar/models/StateModel.js           |   93 +-
 .../js/toolbar/views/AuralView.es6.js         |  104 ++
 .../contextual/js/toolbar/views/AuralView.js  |   70 +-
 .../js/toolbar/views/VisualView.es6.js        |   84 +
 .../contextual/js/toolbar/views/VisualView.js |   66 +-
 .../contextual/js/views/AuralView.es6.js      |   55 +
 core/modules/contextual/js/views/AuralView.js |   51 +-
 .../contextual/js/views/KeyboardView.es6.js   |   61 +
 .../contextual/js/views/KeyboardView.js       |   51 +-
 .../contextual/js/views/RegionView.es6.js     |   57 +
 .../modules/contextual/js/views/RegionView.js |   48 +-
 .../contextual/js/views/VisualView.es6.js     |   80 +
 .../modules/contextual/js/views/VisualView.js |   73 +-
 core/modules/editor/js/editor.admin.es6.js    |  935 ++++++++++
 core/modules/editor/js/editor.admin.js        |  680 +------
 core/modules/editor/js/editor.dialog.es6.js   |   34 +
 core/modules/editor/js/editor.dialog.js       |   32 +-
 core/modules/editor/js/editor.es6.js          |  318 ++++
 .../js/editor.formattedTextEditor.es6.js      |  231 +++
 .../editor/js/editor.formattedTextEditor.js   |  146 +-
 core/modules/editor/js/editor.js              |  231 +--
 core/modules/field_ui/field_ui.es6.js         |  335 ++++
 core/modules/field_ui/field_ui.js             |  201 +--
 core/modules/file/file.es6.js                 |  257 +++
 core/modules/file/file.js                     |  163 +-
 core/modules/filter/filter.admin.es6.js       |   69 +
 core/modules/filter/filter.admin.js           |   43 +-
 core/modules/filter/filter.es6.js             |   39 +
 .../filter/filter.filter_html.admin.es6.js    |  328 ++++
 .../filter/filter.filter_html.admin.js        |  236 +--
 core/modules/filter/filter.js                 |   34 +-
 core/modules/history/js/history.es6.js        |  134 ++
 core/modules/history/js/history.js            |   82 +-
 core/modules/history/js/mark-as-read.es6.js   |   23 +
 core/modules/history/js/mark-as-read.js       |   19 +-
 core/modules/image/js/editors/image.es6.js    |  342 ++++
 core/modules/image/js/editors/image.js        |  192 +-
 core/modules/image/js/theme.es6.js            |   86 +
 core/modules/image/js/theme.js                |   73 +-
 core/modules/language/language.admin.es6.js   |   43 +
 core/modules/language/language.admin.js       |   34 +-
 core/modules/locale/locale.admin.es6.js       |  116 ++
 core/modules/locale/locale.admin.js           |   67 +-
 core/modules/locale/locale.bulk.es6.js        |   38 +
 core/modules/locale/locale.bulk.js            |   39 +-
 core/modules/locale/locale.datepicker.es6.js  |   88 +
 core/modules/locale/locale.datepicker.js      |   82 +-
 core/modules/locale/tests/locale_test.es6.js  |   52 +
 core/modules/locale/tests/locale_test.js      |   45 +-
 core/modules/media/js/media_form.es6.js       |   40 +
 core/modules/media/js/media_form.js           |   34 +-
 core/modules/media/js/media_type_form.es6.js  |   46 +
 core/modules/media/js/media_type_form.js      |   24 +-
 core/modules/menu_ui/menu_ui.admin.es6.js     |   68 +
 core/modules/menu_ui/menu_ui.admin.js         |   40 +-
 core/modules/menu_ui/menu_ui.es6.js           |   91 +
 core/modules/menu_ui/menu_ui.js               |   55 +-
 core/modules/node/content_types.es6.js        |   62 +
 core/modules/node/content_types.js            |   24 +-
 core/modules/node/node.es6.js                 |   55 +
 core/modules/node/node.js                     |   37 +-
 core/modules/node/node.preview.es6.js         |   99 ++
 core/modules/node/node.preview.js             |   75 +-
 core/modules/outside_in/js/off-canvas.es6.js  |  160 ++
 core/modules/outside_in/js/off-canvas.js      |   91 +-
 core/modules/outside_in/js/outside_in.es6.js  |  265 +++
 core/modules/outside_in/js/outside_in.js      |  225 +--
 core/modules/path/path.es6.js                 |   29 +
 core/modules/path/path.js                     |   27 +-
 .../quickedit/js/editors/formEditor.es6.js    |  255 +++
 .../quickedit/js/editors/formEditor.js        |  154 +-
 .../js/editors/plainTextEditor.es6.js         |  144 ++
 .../quickedit/js/editors/plainTextEditor.js   |   71 +-
 .../quickedit/js/models/AppModel.es6.js       |   57 +
 core/modules/quickedit/js/models/AppModel.js  |   52 +-
 .../quickedit/js/models/BaseModel.es6.js      |   60 +
 core/modules/quickedit/js/models/BaseModel.js |   51 +-
 .../quickedit/js/models/EditorModel.es6.js    |   54 +
 .../quickedit/js/models/EditorModel.js        |   49 +-
 .../quickedit/js/models/EntityModel.es6.js    |  741 ++++++++
 .../quickedit/js/models/EntityModel.js        |  571 +-----
 .../quickedit/js/models/FieldModel.es6.js     |  348 ++++
 .../modules/quickedit/js/models/FieldModel.js |  292 +---
 core/modules/quickedit/js/quickedit.es6.js    |  686 ++++++++
 core/modules/quickedit/js/quickedit.js        |  466 +----
 core/modules/quickedit/js/theme.es6.js        |  187 ++
 core/modules/quickedit/js/theme.js            |  118 +-
 core/modules/quickedit/js/util.es6.js         |  213 +++
 core/modules/quickedit/js/util.js             |  151 +-
 .../modules/quickedit/js/views/AppView.es6.js |  600 +++++++
 core/modules/quickedit/js/views/AppView.js    |  499 ++----
 .../js/views/ContextualLinkView.es6.js        |   81 +
 .../quickedit/js/views/ContextualLinkView.js  |   63 +-
 .../quickedit/js/views/EditorView.es6.js      |  304 ++++
 core/modules/quickedit/js/views/EditorView.js |  217 +--
 .../js/views/EntityDecorationView.es6.js      |   40 +
 .../js/views/EntityDecorationView.js          |   34 +-
 .../js/views/EntityToolbarView.es6.js         |  528 ++++++
 .../quickedit/js/views/EntityToolbarView.js   |  388 +---
 .../js/views/FieldDecorationView.es6.js       |  360 ++++
 .../quickedit/js/views/FieldDecorationView.js |  238 +--
 .../js/views/FieldToolbarView.es6.js          |  227 +++
 .../quickedit/js/views/FieldToolbarView.js    |  154 +-
 .../js/responsive_image.ajax.es6.js           |   16 +
 .../js/responsive_image.ajax.js               |   16 +-
 core/modules/simpletest/simpletest.es6.js     |  130 ++
 core/modules/simpletest/simpletest.js         |   81 +-
 core/modules/statistics/statistics.es6.js     |   18 +
 core/modules/statistics/statistics.js         |   11 +-
 core/modules/system/js/system.date.es6.js     |   57 +
 core/modules/system/js/system.date.js         |   36 +-
 core/modules/system/js/system.es6.js          |   81 +
 core/modules/system/js/system.js              |   57 +-
 core/modules/system/js/system.modules.es6.js  |  103 ++
 core/modules/system/js/system.modules.js      |   53 +-
 ...ebassert_test.wait_for_ajax_request.es6.js |   22 +
 ...js_webassert_test.wait_for_ajax_request.js |   20 +-
 .../js_webassert_test.wait_for_element.es6.js |   22 +
 .../js/js_webassert_test.wait_for_element.js  |   20 +-
 .../twig_theme_test/twig_theme_test.es6.js    |    6 +
 .../twig_theme_test/twig_theme_test.js        |   11 +-
 .../themes/test_theme/js/collapse.es6.js      |    4 +
 .../tests/themes/test_theme/js/collapse.js    |    9 +-
 core/modules/taxonomy/taxonomy.es6.js         |   56 +
 core/modules/taxonomy/taxonomy.js             |   28 +-
 core/modules/text/text.es6.js                 |   61 +
 core/modules/text/text.js                     |   29 +-
 core/modules/toolbar/js/escapeAdmin.es6.js    |   48 +
 core/modules/toolbar/js/escapeAdmin.js        |   32 +-
 .../toolbar/js/models/MenuModel.es6.js        |   33 +
 core/modules/toolbar/js/models/MenuModel.js   |   33 +-
 .../toolbar/js/models/ToolbarModel.es6.js     |  157 ++
 .../modules/toolbar/js/models/ToolbarModel.js |  128 +-
 core/modules/toolbar/js/toolbar.es6.js        |  257 +++
 core/modules/toolbar/js/toolbar.js            |  185 +-
 core/modules/toolbar/js/toolbar.menu.es6.js   |  197 +++
 core/modules/toolbar/js/toolbar.menu.js       |  133 +-
 .../toolbar/js/views/BodyVisualView.es6.js    |   53 +
 .../toolbar/js/views/BodyVisualView.js        |   48 +-
 .../toolbar/js/views/MenuVisualView.es6.js    |   46 +
 .../toolbar/js/views/MenuVisualView.js        |   42 +-
 .../toolbar/js/views/ToolbarAuralView.es6.js  |   70 +
 .../toolbar/js/views/ToolbarAuralView.js      |   58 +-
 .../toolbar/js/views/ToolbarVisualView.es6.js |  305 ++++
 .../toolbar/js/views/ToolbarVisualView.js     |  243 +--
 core/modules/tour/js/tour.es6.js              |  270 +++
 core/modules/tour/js/tour.js                  |  231 +--
 .../modules/tracker/js/tracker-history.es6.js |  122 ++
 core/modules/tracker/js/tracker-history.js    |   94 +-
 core/modules/user/user.es6.js                 |  217 +++
 core/modules/user/user.js                     |  101 +-
 core/modules/user/user.permissions.es6.js     |   88 +
 core/modules/user/user.permissions.js         |   69 +-
 core/modules/views/js/ajax_view.es6.js        |  205 +++
 core/modules/views/js/ajax_view.js            |  122 +-
 core/modules/views/js/base.es6.js             |  110 ++
 core/modules/views/js/base.js                 |   63 +-
 .../views_test_data/views_cache.test.es6.js   |    8 +
 .../views_test_data/views_cache.test.js       |   13 +-
 core/modules/views_ui/js/ajax.es6.js          |  249 +++
 core/modules/views_ui/js/ajax.js              |  206 +--
 core/modules/views_ui/js/dialog.views.es6.js  |   58 +
 core/modules/views_ui/js/dialog.views.js      |   40 +-
 core/modules/views_ui/js/views-admin.es6.js   | 1192 +++++++++++++
 core/modules/views_ui/js/views-admin.js       |  739 ++------
 .../views_ui/js/views_ui.listing.es6.js       |   54 +
 core/modules/views_ui/js/views_ui.listing.js  |   30 +-
 core/package.json                             |    1 +
 core/scripts/js/changeOrAdded.js              |    9 +-
 core/scripts/js/rename-js-files-to-es6.sh     |   12 -
 core/themes/bartik/color/preview.es6.js       |   49 +
 core/themes/bartik/color/preview.js           |   22 +-
 core/themes/seven/js/mobile.install.es6.js    |   33 +
 core/themes/seven/js/mobile.install.js        |   13 +-
 core/themes/seven/js/nav-tabs.es6.js          |   55 +
 core/themes/seven/js/nav-tabs.js              |   21 +-
 .../themes/seven/js/responsive-details.es6.js |   57 +
 core/themes/seven/js/responsive-details.js    |   37 +-
 core/yarn.lock                                |    4 +
 298 files changed, 31056 insertions(+), 14996 deletions(-)
 create mode 100644 core/misc/active-link.es6.js
 create mode 100644 core/misc/ajax.es6.js
 create mode 100644 core/misc/announce.es6.js
 create mode 100644 core/misc/autocomplete.es6.js
 create mode 100644 core/misc/batch.es6.js
 create mode 100644 core/misc/collapse.es6.js
 create mode 100644 core/misc/date.es6.js
 create mode 100644 core/misc/debounce.es6.js
 create mode 100644 core/misc/details-aria.es6.js
 create mode 100644 core/misc/dialog/dialog.ajax.es6.js
 create mode 100644 core/misc/dialog/dialog.es6.js
 create mode 100644 core/misc/dialog/dialog.jquery-ui.es6.js
 create mode 100644 core/misc/dialog/dialog.position.es6.js
 create mode 100644 core/misc/displace.es6.js
 create mode 100644 core/misc/dropbutton/dropbutton.es6.js
 create mode 100644 core/misc/drupal.es6.js
 create mode 100644 core/misc/drupal.init.es6.js
 create mode 100644 core/misc/drupalSettingsLoader.es6.js
 create mode 100644 core/misc/entity-form.es6.js
 create mode 100644 core/misc/form.es6.js
 create mode 100644 core/misc/machine-name.es6.js
 create mode 100644 core/misc/progress.es6.js
 create mode 100644 core/misc/states.es6.js
 create mode 100644 core/misc/tabbingmanager.es6.js
 create mode 100644 core/misc/tabledrag.es6.js
 create mode 100644 core/misc/tableheader.es6.js
 create mode 100644 core/misc/tableresponsive.es6.js
 create mode 100644 core/misc/tableselect.es6.js
 create mode 100644 core/misc/timezone.es6.js
 create mode 100644 core/misc/vertical-tabs.es6.js
 create mode 100644 core/modules/big_pipe/js/big_pipe.es6.js
 create mode 100644 core/modules/block/js/block.admin.es6.js
 create mode 100644 core/modules/block/js/block.es6.js
 create mode 100644 core/modules/book/book.es6.js
 create mode 100644 core/modules/ckeditor/js/ckeditor.admin.es6.js
 create mode 100644 core/modules/ckeditor/js/ckeditor.drupalimage.admin.es6.js
 create mode 100644 core/modules/ckeditor/js/ckeditor.es6.js
 create mode 100644 core/modules/ckeditor/js/ckeditor.language.admin.es6.js
 create mode 100644 core/modules/ckeditor/js/ckeditor.stylescombo.admin.es6.js
 create mode 100644 core/modules/ckeditor/js/models/Model.es6.js
 create mode 100644 core/modules/ckeditor/js/plugins/drupalimage/plugin.es6.js
 create mode 100644 core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.es6.js
 create mode 100644 core/modules/ckeditor/js/plugins/drupallink/plugin.es6.js
 create mode 100644 core/modules/ckeditor/js/views/AuralView.es6.js
 create mode 100644 core/modules/ckeditor/js/views/ControllerView.es6.js
 create mode 100644 core/modules/ckeditor/js/views/KeyboardView.es6.js
 create mode 100644 core/modules/ckeditor/js/views/VisualView.es6.js
 create mode 100644 core/modules/ckeditor/tests/modules/js/ajax-css.es6.js
 create mode 100644 core/modules/color/color.es6.js
 create mode 100644 core/modules/color/preview.es6.js
 create mode 100644 core/modules/color/tests/modules/color_test/themes/color_test_theme/js/color_test_theme-fontsize.es6.js
 create mode 100644 core/modules/comment/comment-entity-form.es6.js
 create mode 100644 core/modules/comment/js/comment-by-viewer.es6.js
 create mode 100644 core/modules/comment/js/comment-new-indicator.es6.js
 create mode 100644 core/modules/comment/js/node-new-comments-link.es6.js
 create mode 100644 core/modules/content_translation/content_translation.admin.es6.js
 create mode 100644 core/modules/contextual/js/contextual.es6.js
 create mode 100644 core/modules/contextual/js/contextual.toolbar.es6.js
 create mode 100644 core/modules/contextual/js/models/StateModel.es6.js
 create mode 100644 core/modules/contextual/js/toolbar/models/StateModel.es6.js
 create mode 100644 core/modules/contextual/js/toolbar/views/AuralView.es6.js
 create mode 100644 core/modules/contextual/js/toolbar/views/VisualView.es6.js
 create mode 100644 core/modules/contextual/js/views/AuralView.es6.js
 create mode 100644 core/modules/contextual/js/views/KeyboardView.es6.js
 create mode 100644 core/modules/contextual/js/views/RegionView.es6.js
 create mode 100644 core/modules/contextual/js/views/VisualView.es6.js
 create mode 100644 core/modules/editor/js/editor.admin.es6.js
 create mode 100644 core/modules/editor/js/editor.dialog.es6.js
 create mode 100644 core/modules/editor/js/editor.es6.js
 create mode 100644 core/modules/editor/js/editor.formattedTextEditor.es6.js
 create mode 100644 core/modules/field_ui/field_ui.es6.js
 create mode 100644 core/modules/file/file.es6.js
 create mode 100644 core/modules/filter/filter.admin.es6.js
 create mode 100644 core/modules/filter/filter.es6.js
 create mode 100644 core/modules/filter/filter.filter_html.admin.es6.js
 create mode 100644 core/modules/history/js/history.es6.js
 create mode 100644 core/modules/history/js/mark-as-read.es6.js
 create mode 100644 core/modules/image/js/editors/image.es6.js
 create mode 100644 core/modules/image/js/theme.es6.js
 create mode 100644 core/modules/language/language.admin.es6.js
 create mode 100644 core/modules/locale/locale.admin.es6.js
 create mode 100644 core/modules/locale/locale.bulk.es6.js
 create mode 100644 core/modules/locale/locale.datepicker.es6.js
 create mode 100644 core/modules/locale/tests/locale_test.es6.js
 create mode 100644 core/modules/media/js/media_form.es6.js
 create mode 100644 core/modules/media/js/media_type_form.es6.js
 create mode 100644 core/modules/menu_ui/menu_ui.admin.es6.js
 create mode 100644 core/modules/menu_ui/menu_ui.es6.js
 create mode 100644 core/modules/node/content_types.es6.js
 create mode 100644 core/modules/node/node.es6.js
 create mode 100644 core/modules/node/node.preview.es6.js
 create mode 100644 core/modules/outside_in/js/off-canvas.es6.js
 create mode 100644 core/modules/outside_in/js/outside_in.es6.js
 create mode 100644 core/modules/path/path.es6.js
 create mode 100644 core/modules/quickedit/js/editors/formEditor.es6.js
 create mode 100644 core/modules/quickedit/js/editors/plainTextEditor.es6.js
 create mode 100644 core/modules/quickedit/js/models/AppModel.es6.js
 create mode 100644 core/modules/quickedit/js/models/BaseModel.es6.js
 create mode 100644 core/modules/quickedit/js/models/EditorModel.es6.js
 create mode 100644 core/modules/quickedit/js/models/EntityModel.es6.js
 create mode 100644 core/modules/quickedit/js/models/FieldModel.es6.js
 create mode 100644 core/modules/quickedit/js/quickedit.es6.js
 create mode 100644 core/modules/quickedit/js/theme.es6.js
 create mode 100644 core/modules/quickedit/js/util.es6.js
 create mode 100644 core/modules/quickedit/js/views/AppView.es6.js
 create mode 100644 core/modules/quickedit/js/views/ContextualLinkView.es6.js
 create mode 100644 core/modules/quickedit/js/views/EditorView.es6.js
 create mode 100644 core/modules/quickedit/js/views/EntityDecorationView.es6.js
 create mode 100644 core/modules/quickedit/js/views/EntityToolbarView.es6.js
 create mode 100644 core/modules/quickedit/js/views/FieldDecorationView.es6.js
 create mode 100644 core/modules/quickedit/js/views/FieldToolbarView.es6.js
 create mode 100644 core/modules/responsive_image/js/responsive_image.ajax.es6.js
 create mode 100644 core/modules/simpletest/simpletest.es6.js
 create mode 100644 core/modules/statistics/statistics.es6.js
 create mode 100644 core/modules/system/js/system.date.es6.js
 create mode 100644 core/modules/system/js/system.es6.js
 create mode 100644 core/modules/system/js/system.modules.es6.js
 create mode 100644 core/modules/system/tests/modules/js_webassert_test/js/js_webassert_test.wait_for_ajax_request.es6.js
 create mode 100644 core/modules/system/tests/modules/js_webassert_test/js/js_webassert_test.wait_for_element.es6.js
 create mode 100644 core/modules/system/tests/modules/twig_theme_test/twig_theme_test.es6.js
 create mode 100644 core/modules/system/tests/themes/test_theme/js/collapse.es6.js
 create mode 100644 core/modules/taxonomy/taxonomy.es6.js
 create mode 100644 core/modules/text/text.es6.js
 create mode 100644 core/modules/toolbar/js/escapeAdmin.es6.js
 create mode 100644 core/modules/toolbar/js/models/MenuModel.es6.js
 create mode 100644 core/modules/toolbar/js/models/ToolbarModel.es6.js
 create mode 100644 core/modules/toolbar/js/toolbar.es6.js
 create mode 100644 core/modules/toolbar/js/toolbar.menu.es6.js
 create mode 100644 core/modules/toolbar/js/views/BodyVisualView.es6.js
 create mode 100644 core/modules/toolbar/js/views/MenuVisualView.es6.js
 create mode 100644 core/modules/toolbar/js/views/ToolbarAuralView.es6.js
 create mode 100644 core/modules/toolbar/js/views/ToolbarVisualView.es6.js
 create mode 100644 core/modules/tour/js/tour.es6.js
 create mode 100644 core/modules/tracker/js/tracker-history.es6.js
 create mode 100644 core/modules/user/user.es6.js
 create mode 100644 core/modules/user/user.permissions.es6.js
 create mode 100644 core/modules/views/js/ajax_view.es6.js
 create mode 100644 core/modules/views/js/base.es6.js
 create mode 100644 core/modules/views/tests/modules/views_test_data/views_cache.test.es6.js
 create mode 100644 core/modules/views_ui/js/ajax.es6.js
 create mode 100644 core/modules/views_ui/js/dialog.views.es6.js
 create mode 100644 core/modules/views_ui/js/views-admin.es6.js
 create mode 100644 core/modules/views_ui/js/views_ui.listing.es6.js
 delete mode 100644 core/scripts/js/rename-js-files-to-es6.sh
 create mode 100644 core/themes/bartik/color/preview.es6.js
 create mode 100644 core/themes/seven/js/mobile.install.es6.js
 create mode 100644 core/themes/seven/js/nav-tabs.es6.js
 create mode 100644 core/themes/seven/js/responsive-details.es6.js

diff --git a/core/misc/active-link.es6.js b/core/misc/active-link.es6.js
new file mode 100644
index 000000000000..9cf55b43444f
--- /dev/null
+++ b/core/misc/active-link.es6.js
@@ -0,0 +1,68 @@
+/**
+ * @file
+ * Attaches behaviors for Drupal's active link marking.
+ */
+
+(function (Drupal, drupalSettings) {
+
+  'use strict';
+
+  /**
+   * Append is-active class.
+   *
+   * The link is only active if its path corresponds to the current path, the
+   * language of the linked path is equal to the current language, and if the
+   * query parameters of the link equal those of the current request, since the
+   * same request with different query parameters may yield a different page
+   * (e.g. pagers, exposed View filters).
+   *
+   * Does not discriminate based on element type, so allows you to set the
+   * is-active class on any element: a, li…
+   *
+   * @type {Drupal~behavior}
+   */
+  Drupal.behaviors.activeLinks = {
+    attach: function (context) {
+      // Start by finding all potentially active links.
+      var path = drupalSettings.path;
+      var queryString = JSON.stringify(path.currentQuery);
+      var querySelector = path.currentQuery ? "[data-drupal-link-query='" + queryString + "']" : ':not([data-drupal-link-query])';
+      var originalSelectors = ['[data-drupal-link-system-path="' + path.currentPath + '"]'];
+      var selectors;
+
+      // If this is the front page, we have to check for the <front> path as
+      // well.
+      if (path.isFront) {
+        originalSelectors.push('[data-drupal-link-system-path="<front>"]');
+      }
+
+      // Add language filtering.
+      selectors = [].concat(
+        // Links without any hreflang attributes (most of them).
+        originalSelectors.map(function (selector) { return selector + ':not([hreflang])'; }),
+        // Links with hreflang equals to the current language.
+        originalSelectors.map(function (selector) { return selector + '[hreflang="' + path.currentLanguage + '"]'; })
+      );
+
+      // Add query string selector for pagers, exposed filters.
+      selectors = selectors.map(function (current) { return current + querySelector; });
+
+      // Query the DOM.
+      var activeLinks = context.querySelectorAll(selectors.join(','));
+      var il = activeLinks.length;
+      for (var i = 0; i < il; i++) {
+        activeLinks[i].classList.add('is-active');
+      }
+    },
+    detach: function (context, settings, trigger) {
+      if (trigger === 'unload') {
+        var activeLinks = context.querySelectorAll('[data-drupal-link-system-path].is-active');
+        var il = activeLinks.length;
+        for (var i = 0; i < il; i++) {
+          activeLinks[i].classList.remove('is-active');
+        }
+      }
+    }
+  };
+
+})(Drupal, drupalSettings);
diff --git a/core/misc/active-link.js b/core/misc/active-link.js
index 9cf55b43444f..9251c4e8cb21 100644
--- a/core/misc/active-link.js
+++ b/core/misc/active-link.js
@@ -1,60 +1,44 @@
 /**
- * @file
- * Attaches behaviors for Drupal's active link marking.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./misc/active-link.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function (Drupal, drupalSettings) {
 
   'use strict';
 
-  /**
-   * Append is-active class.
-   *
-   * The link is only active if its path corresponds to the current path, the
-   * language of the linked path is equal to the current language, and if the
-   * query parameters of the link equal those of the current request, since the
-   * same request with different query parameters may yield a different page
-   * (e.g. pagers, exposed View filters).
-   *
-   * Does not discriminate based on element type, so allows you to set the
-   * is-active class on any element: a, li…
-   *
-   * @type {Drupal~behavior}
-   */
   Drupal.behaviors.activeLinks = {
-    attach: function (context) {
-      // Start by finding all potentially active links.
+    attach: function attach(context) {
       var path = drupalSettings.path;
       var queryString = JSON.stringify(path.currentQuery);
       var querySelector = path.currentQuery ? "[data-drupal-link-query='" + queryString + "']" : ':not([data-drupal-link-query])';
       var originalSelectors = ['[data-drupal-link-system-path="' + path.currentPath + '"]'];
       var selectors;
 
-      // If this is the front page, we have to check for the <front> path as
-      // well.
       if (path.isFront) {
         originalSelectors.push('[data-drupal-link-system-path="<front>"]');
       }
 
-      // Add language filtering.
-      selectors = [].concat(
-        // Links without any hreflang attributes (most of them).
-        originalSelectors.map(function (selector) { return selector + ':not([hreflang])'; }),
-        // Links with hreflang equals to the current language.
-        originalSelectors.map(function (selector) { return selector + '[hreflang="' + path.currentLanguage + '"]'; })
-      );
+      selectors = [].concat(originalSelectors.map(function (selector) {
+        return selector + ':not([hreflang])';
+      }), originalSelectors.map(function (selector) {
+        return selector + '[hreflang="' + path.currentLanguage + '"]';
+      }));
 
-      // Add query string selector for pagers, exposed filters.
-      selectors = selectors.map(function (current) { return current + querySelector; });
+      selectors = selectors.map(function (current) {
+        return current + querySelector;
+      });
 
-      // Query the DOM.
       var activeLinks = context.querySelectorAll(selectors.join(','));
       var il = activeLinks.length;
       for (var i = 0; i < il; i++) {
         activeLinks[i].classList.add('is-active');
       }
     },
-    detach: function (context, settings, trigger) {
+    detach: function detach(context, settings, trigger) {
       if (trigger === 'unload') {
         var activeLinks = context.querySelectorAll('[data-drupal-link-system-path].is-active');
         var il = activeLinks.length;
@@ -64,5 +48,4 @@
       }
     }
   };
-
-})(Drupal, drupalSettings);
+})(Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/misc/ajax.es6.js b/core/misc/ajax.es6.js
new file mode 100644
index 000000000000..fefe9f3031ef
--- /dev/null
+++ b/core/misc/ajax.es6.js
@@ -0,0 +1,1344 @@
+/**
+ * @file
+ * Provides Ajax page updating via jQuery $.ajax.
+ *
+ * Ajax is a method of making a request via JavaScript while viewing an HTML
+ * page. The request returns an array of commands encoded in JSON, which is
+ * then executed to make any changes that are necessary to the page.
+ *
+ * Drupal uses this file to enhance form elements with `#ajax['url']` and
+ * `#ajax['wrapper']` properties. If set, this file will automatically be
+ * included to provide Ajax capabilities.
+ */
+
+(function ($, window, Drupal, drupalSettings) {
+
+  'use strict';
+
+  /**
+   * Attaches the Ajax behavior to each Ajax form element.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Initialize all {@link Drupal.Ajax} objects declared in
+   *   `drupalSettings.ajax` or initialize {@link Drupal.Ajax} objects from
+   *   DOM elements having the `use-ajax-submit` or `use-ajax` css class.
+   * @prop {Drupal~behaviorDetach} detach
+   *   During `unload` remove all {@link Drupal.Ajax} objects related to
+   *   the removed content.
+   */
+  Drupal.behaviors.AJAX = {
+    attach: function (context, settings) {
+
+      function loadAjaxBehavior(base) {
+        var element_settings = settings.ajax[base];
+        if (typeof element_settings.selector === 'undefined') {
+          element_settings.selector = '#' + base;
+        }
+        $(element_settings.selector).once('drupal-ajax').each(function () {
+          element_settings.element = this;
+          element_settings.base = base;
+          Drupal.ajax(element_settings);
+        });
+      }
+
+      // Load all Ajax behaviors specified in the settings.
+      for (var base in settings.ajax) {
+        if (settings.ajax.hasOwnProperty(base)) {
+          loadAjaxBehavior(base);
+        }
+      }
+
+      // Bind Ajax behaviors to all items showing the class.
+      $('.use-ajax').once('ajax').each(function () {
+        var element_settings = {};
+        // Clicked links look better with the throbber than the progress bar.
+        element_settings.progress = {type: 'throbber'};
+
+        // For anchor tags, these will go to the target of the anchor rather
+        // than the usual location.
+        var href = $(this).attr('href');
+        if (href) {
+          element_settings.url = href;
+          element_settings.event = 'click';
+        }
+        element_settings.dialogType = $(this).data('dialog-type');
+        element_settings.dialog = $(this).data('dialog-options');
+        element_settings.base = $(this).attr('id');
+        element_settings.element = this;
+        Drupal.ajax(element_settings);
+      });
+
+      // This class means to submit the form to the action using Ajax.
+      $('.use-ajax-submit').once('ajax').each(function () {
+        var element_settings = {};
+
+        // Ajax submits specified in this manner automatically submit to the
+        // normal form action.
+        element_settings.url = $(this.form).attr('action');
+        // Form submit button clicks need to tell the form what was clicked so
+        // it gets passed in the POST request.
+        element_settings.setClick = true;
+        // Form buttons use the 'click' event rather than mousedown.
+        element_settings.event = 'click';
+        // Clicked form buttons look better with the throbber than the progress
+        // bar.
+        element_settings.progress = {type: 'throbber'};
+        element_settings.base = $(this).attr('id');
+        element_settings.element = this;
+
+        Drupal.ajax(element_settings);
+      });
+    },
+
+    detach: function (context, settings, trigger) {
+      if (trigger === 'unload') {
+        Drupal.ajax.expired().forEach(function (instance) {
+          // Set this to null and allow garbage collection to reclaim
+          // the memory.
+          Drupal.ajax.instances[instance.instanceIndex] = null;
+        });
+      }
+    }
+  };
+
+  /**
+   * Extends Error to provide handling for Errors in Ajax.
+   *
+   * @constructor
+   *
+   * @augments Error
+   *
+   * @param {XMLHttpRequest} xmlhttp
+   *   XMLHttpRequest object used for the failed request.
+   * @param {string} uri
+   *   The URI where the error occurred.
+   * @param {string} customMessage
+   *   The custom message.
+   */
+  Drupal.AjaxError = function (xmlhttp, uri, customMessage) {
+
+    var statusCode;
+    var statusText;
+    var pathText;
+    var responseText;
+    var readyStateText;
+    if (xmlhttp.status) {
+      statusCode = '\n' + Drupal.t('An AJAX HTTP error occurred.') + '\n' + Drupal.t('HTTP Result Code: !status', {'!status': xmlhttp.status});
+    }
+    else {
+      statusCode = '\n' + Drupal.t('An AJAX HTTP request terminated abnormally.');
+    }
+    statusCode += '\n' + Drupal.t('Debugging information follows.');
+    pathText = '\n' + Drupal.t('Path: !uri', {'!uri': uri});
+    statusText = '';
+    // In some cases, when statusCode === 0, xmlhttp.statusText may not be
+    // defined. Unfortunately, testing for it with typeof, etc, doesn't seem to
+    // catch that and the test causes an exception. So we need to catch the
+    // exception here.
+    try {
+      statusText = '\n' + Drupal.t('StatusText: !statusText', {'!statusText': $.trim(xmlhttp.statusText)});
+    }
+    catch (e) {
+      // Empty.
+    }
+
+    responseText = '';
+    // Again, we don't have a way to know for sure whether accessing
+    // xmlhttp.responseText is going to throw an exception. So we'll catch it.
+    try {
+      responseText = '\n' + Drupal.t('ResponseText: !responseText', {'!responseText': $.trim(xmlhttp.responseText)});
+    }
+    catch (e) {
+      // Empty.
+    }
+
+    // Make the responseText more readable by stripping HTML tags and newlines.
+    responseText = responseText.replace(/<("[^"]*"|'[^']*'|[^'">])*>/gi, '');
+    responseText = responseText.replace(/[\n]+\s+/g, '\n');
+
+    // We don't need readyState except for status == 0.
+    readyStateText = xmlhttp.status === 0 ? ('\n' + Drupal.t('ReadyState: !readyState', {'!readyState': xmlhttp.readyState})) : '';
+
+    customMessage = customMessage ? ('\n' + Drupal.t('CustomMessage: !customMessage', {'!customMessage': customMessage})) : '';
+
+    /**
+     * Formatted and translated error message.
+     *
+     * @type {string}
+     */
+    this.message = statusCode + pathText + statusText + customMessage + responseText + readyStateText;
+
+    /**
+     * Used by some browsers to display a more accurate stack trace.
+     *
+     * @type {string}
+     */
+    this.name = 'AjaxError';
+  };
+
+  Drupal.AjaxError.prototype = new Error();
+  Drupal.AjaxError.prototype.constructor = Drupal.AjaxError;
+
+  /**
+   * Provides Ajax page updating via jQuery $.ajax.
+   *
+   * This function is designed to improve developer experience by wrapping the
+   * initialization of {@link Drupal.Ajax} objects and storing all created
+   * objects in the {@link Drupal.ajax.instances} array.
+   *
+   * @example
+   * Drupal.behaviors.myCustomAJAXStuff = {
+   *   attach: function (context, settings) {
+   *
+   *     var ajaxSettings = {
+   *       url: 'my/url/path',
+   *       // If the old version of Drupal.ajax() needs to be used those
+   *       // properties can be added
+   *       base: 'myBase',
+   *       element: $(context).find('.someElement')
+   *     };
+   *
+   *     var myAjaxObject = Drupal.ajax(ajaxSettings);
+   *
+   *     // Declare a new Ajax command specifically for this Ajax object.
+   *     myAjaxObject.commands.insert = function (ajax, response, status) {
+   *       $('#my-wrapper').append(response.data);
+   *       alert('New content was appended to #my-wrapper');
+   *     };
+   *
+   *     // This command will remove this Ajax object from the page.
+   *     myAjaxObject.commands.destroyObject = function (ajax, response, status) {
+   *       Drupal.ajax.instances[this.instanceIndex] = null;
+   *     };
+   *
+   *     // Programmatically trigger the Ajax request.
+   *     myAjaxObject.execute();
+   *   }
+   * };
+   *
+   * @param {object} settings
+   *   The settings object passed to {@link Drupal.Ajax} constructor.
+   * @param {string} [settings.base]
+   *   Base is passed to {@link Drupal.Ajax} constructor as the 'base'
+   *   parameter.
+   * @param {HTMLElement} [settings.element]
+   *   Element parameter of {@link Drupal.Ajax} constructor, element on which
+   *   event listeners will be bound.
+   *
+   * @return {Drupal.Ajax}
+   *   The created Ajax object.
+   *
+   * @see Drupal.AjaxCommands
+   */
+  Drupal.ajax = function (settings) {
+    if (arguments.length !== 1) {
+      throw new Error('Drupal.ajax() function must be called with one configuration object only');
+    }
+    // Map those config keys to variables for the old Drupal.ajax function.
+    var base = settings.base || false;
+    var element = settings.element || false;
+    delete settings.base;
+    delete settings.element;
+
+    // By default do not display progress for ajax calls without an element.
+    if (!settings.progress && !element) {
+      settings.progress = false;
+    }
+
+    var ajax = new Drupal.Ajax(base, element, settings);
+    ajax.instanceIndex = Drupal.ajax.instances.length;
+    Drupal.ajax.instances.push(ajax);
+
+    return ajax;
+  };
+
+  /**
+   * Contains all created Ajax objects.
+   *
+   * @type {Array.<Drupal.Ajax|null>}
+   */
+  Drupal.ajax.instances = [];
+
+  /**
+   * List all objects where the associated element is not in the DOM
+   *
+   * This method ignores {@link Drupal.Ajax} objects not bound to DOM elements
+   * when created with {@link Drupal.ajax}.
+   *
+   * @return {Array.<Drupal.Ajax>}
+   *   The list of expired {@link Drupal.Ajax} objects.
+   */
+  Drupal.ajax.expired = function () {
+    return Drupal.ajax.instances.filter(function (instance) {
+      return instance && instance.element !== false && !document.body.contains(instance.element);
+    });
+  };
+
+  /**
+   * Settings for an Ajax object.
+   *
+   * @typedef {object} Drupal.Ajax~element_settings
+   *
+   * @prop {string} url
+   *   Target of the Ajax request.
+   * @prop {?string} [event]
+   *   Event bound to settings.element which will trigger the Ajax request.
+   * @prop {bool} [keypress=true]
+   *   Triggers a request on keypress events.
+   * @prop {?string} selector
+   *   jQuery selector targeting the element to bind events to or used with
+   *   {@link Drupal.AjaxCommands}.
+   * @prop {string} [effect='none']
+   *   Name of the jQuery method to use for displaying new Ajax content.
+   * @prop {string|number} [speed='none']
+   *   Speed with which to apply the effect.
+   * @prop {string} [method]
+   *   Name of the jQuery method used to insert new content in the targeted
+   *   element.
+   * @prop {object} [progress]
+   *   Settings for the display of a user-friendly loader.
+   * @prop {string} [progress.type='throbber']
+   *   Type of progress element, core provides `'bar'`, `'throbber'` and
+   *   `'fullscreen'`.
+   * @prop {string} [progress.message=Drupal.t('Please wait...')]
+   *   Custom message to be used with the bar indicator.
+   * @prop {object} [submit]
+   *   Extra data to be sent with the Ajax request.
+   * @prop {bool} [submit.js=true]
+   *   Allows the PHP side to know this comes from an Ajax request.
+   * @prop {object} [dialog]
+   *   Options for {@link Drupal.dialog}.
+   * @prop {string} [dialogType]
+   *   One of `'modal'` or `'dialog'`.
+   * @prop {string} [prevent]
+   *   List of events on which to stop default action and stop propagation.
+   */
+
+  /**
+   * Ajax constructor.
+   *
+   * The Ajax request returns an array of commands encoded in JSON, which is
+   * then executed to make any changes that are necessary to the page.
+   *
+   * Drupal uses this file to enhance form elements with `#ajax['url']` and
+   * `#ajax['wrapper']` properties. If set, this file will automatically be
+   * included to provide Ajax capabilities.
+   *
+   * @constructor
+   *
+   * @param {string} [base]
+   *   Base parameter of {@link Drupal.Ajax} constructor
+   * @param {HTMLElement} [element]
+   *   Element parameter of {@link Drupal.Ajax} constructor, element on which
+   *   event listeners will be bound.
+   * @param {Drupal.Ajax~element_settings} element_settings
+   *   Settings for this Ajax object.
+   */
+  Drupal.Ajax = function (base, element, element_settings) {
+    var defaults = {
+      event: element ? 'mousedown' : null,
+      keypress: true,
+      selector: base ? '#' + base : null,
+      effect: 'none',
+      speed: 'none',
+      method: 'replaceWith',
+      progress: {
+        type: 'throbber',
+        message: Drupal.t('Please wait...')
+      },
+      submit: {
+        js: true
+      }
+    };
+
+    $.extend(this, defaults, element_settings);
+
+    /**
+     * @type {Drupal.AjaxCommands}
+     */
+    this.commands = new Drupal.AjaxCommands();
+
+    /**
+     * @type {bool|number}
+     */
+    this.instanceIndex = false;
+
+    // @todo Remove this after refactoring the PHP code to:
+    //   - Call this 'selector'.
+    //   - Include the '#' for ID-based selectors.
+    //   - Support non-ID-based selectors.
+    if (this.wrapper) {
+
+      /**
+       * @type {string}
+       */
+      this.wrapper = '#' + this.wrapper;
+    }
+
+    /**
+     * @type {HTMLElement}
+     */
+    this.element = element;
+
+    /**
+     * @type {Drupal.Ajax~element_settings}
+     */
+    this.element_settings = element_settings;
+
+    // If there isn't a form, jQuery.ajax() will be used instead, allowing us to
+    // bind Ajax to links as well.
+    if (this.element && this.element.form) {
+
+      /**
+       * @type {jQuery}
+       */
+      this.$form = $(this.element.form);
+    }
+
+    // If no Ajax callback URL was given, use the link href or form action.
+    if (!this.url) {
+      var $element = $(this.element);
+      if ($element.is('a')) {
+        this.url = $element.attr('href');
+      }
+      else if (this.element && element.form) {
+        this.url = this.$form.attr('action');
+      }
+    }
+
+    // Replacing 'nojs' with 'ajax' in the URL allows for an easy method to let
+    // the server detect when it needs to degrade gracefully.
+    // There are four scenarios to check for:
+    // 1. /nojs/
+    // 2. /nojs$ - The end of a URL string.
+    // 3. /nojs? - Followed by a query (e.g. path/nojs?destination=foobar).
+    // 4. /nojs# - Followed by a fragment (e.g.: path/nojs#myfragment).
+    var originalUrl = this.url;
+
+    /**
+     * Processed Ajax URL.
+     *
+     * @type {string}
+     */
+    this.url = this.url.replace(/\/nojs(\/|$|\?|#)/g, '/ajax$1');
+    // If the 'nojs' version of the URL is trusted, also trust the 'ajax'
+    // version.
+    if (drupalSettings.ajaxTrustedUrl[originalUrl]) {
+      drupalSettings.ajaxTrustedUrl[this.url] = true;
+    }
+
+    // Set the options for the ajaxSubmit function.
+    // The 'this' variable will not persist inside of the options object.
+    var ajax = this;
+
+    /**
+     * Options for the jQuery.ajax function.
+     *
+     * @name Drupal.Ajax#options
+     *
+     * @type {object}
+     *
+     * @prop {string} url
+     *   Ajax URL to be called.
+     * @prop {object} data
+     *   Ajax payload.
+     * @prop {function} beforeSerialize
+     *   Implement jQuery beforeSerialize function to call
+     *   {@link Drupal.Ajax#beforeSerialize}.
+     * @prop {function} beforeSubmit
+     *   Implement jQuery beforeSubmit function to call
+     *   {@link Drupal.Ajax#beforeSubmit}.
+     * @prop {function} beforeSend
+     *   Implement jQuery beforeSend function to call
+     *   {@link Drupal.Ajax#beforeSend}.
+     * @prop {function} success
+     *   Implement jQuery success function to call
+     *   {@link Drupal.Ajax#success}.
+     * @prop {function} complete
+     *   Implement jQuery success function to clean up ajax state and trigger an
+     *   error if needed.
+     * @prop {string} dataType='json'
+     *   Type of the response expected.
+     * @prop {string} type='POST'
+     *   HTTP method to use for the Ajax request.
+     */
+    ajax.options = {
+      url: ajax.url,
+      data: ajax.submit,
+      beforeSerialize: function (element_settings, options) {
+        return ajax.beforeSerialize(element_settings, options);
+      },
+      beforeSubmit: function (form_values, element_settings, options) {
+        ajax.ajaxing = true;
+        return ajax.beforeSubmit(form_values, element_settings, options);
+      },
+      beforeSend: function (xmlhttprequest, options) {
+        ajax.ajaxing = true;
+        return ajax.beforeSend(xmlhttprequest, options);
+      },
+      success: function (response, status, xmlhttprequest) {
+        // Sanity check for browser support (object expected).
+        // When using iFrame uploads, responses must be returned as a string.
+        if (typeof response === 'string') {
+          response = $.parseJSON(response);
+        }
+
+        // Prior to invoking the response's commands, verify that they can be
+        // trusted by checking for a response header. See
+        // \Drupal\Core\EventSubscriber\AjaxResponseSubscriber for details.
+        // - Empty responses are harmless so can bypass verification. This
+        //   avoids an alert message for server-generated no-op responses that
+        //   skip Ajax rendering.
+        // - Ajax objects with trusted URLs (e.g., ones defined server-side via
+        //   #ajax) can bypass header verification. This is especially useful
+        //   for Ajax with multipart forms. Because IFRAME transport is used,
+        //   the response headers cannot be accessed for verification.
+        if (response !== null && !drupalSettings.ajaxTrustedUrl[ajax.url]) {
+          if (xmlhttprequest.getResponseHeader('X-Drupal-Ajax-Token') !== '1') {
+            var customMessage = Drupal.t('The response failed verification so will not be processed.');
+            return ajax.error(xmlhttprequest, ajax.url, customMessage);
+          }
+        }
+
+        return ajax.success(response, status);
+      },
+      complete: function (xmlhttprequest, status) {
+        ajax.ajaxing = false;
+        if (status === 'error' || status === 'parsererror') {
+          return ajax.error(xmlhttprequest, ajax.url);
+        }
+      },
+      dataType: 'json',
+      type: 'POST'
+    };
+
+    if (element_settings.dialog) {
+      ajax.options.data.dialogOptions = element_settings.dialog;
+    }
+
+    // Ensure that we have a valid URL by adding ? when no query parameter is
+    // yet available, otherwise append using &.
+    if (ajax.options.url.indexOf('?') === -1) {
+      ajax.options.url += '?';
+    }
+    else {
+      ajax.options.url += '&';
+    }
+    ajax.options.url += Drupal.ajax.WRAPPER_FORMAT + '=drupal_' + (element_settings.dialogType || 'ajax');
+
+    // Bind the ajaxSubmit function to the element event.
+    $(ajax.element).on(element_settings.event, function (event) {
+      if (!drupalSettings.ajaxTrustedUrl[ajax.url] && !Drupal.url.isLocal(ajax.url)) {
+        throw new Error(Drupal.t('The callback URL is not local and not trusted: !url', {'!url': ajax.url}));
+      }
+      return ajax.eventResponse(this, event);
+    });
+
+    // If necessary, enable keyboard submission so that Ajax behaviors
+    // can be triggered through keyboard input as well as e.g. a mousedown
+    // action.
+    if (element_settings.keypress) {
+      $(ajax.element).on('keypress', function (event) {
+        return ajax.keypressResponse(this, event);
+      });
+    }
+
+    // If necessary, prevent the browser default action of an additional event.
+    // For example, prevent the browser default action of a click, even if the
+    // Ajax behavior binds to mousedown.
+    if (element_settings.prevent) {
+      $(ajax.element).on(element_settings.prevent, false);
+    }
+  };
+
+  /**
+   * URL query attribute to indicate the wrapper used to render a request.
+   *
+   * The wrapper format determines how the HTML is wrapped, for example in a
+   * modal dialog.
+   *
+   * @const {string}
+   *
+   * @default
+   */
+  Drupal.ajax.WRAPPER_FORMAT = '_wrapper_format';
+
+  /**
+   * Request parameter to indicate that a request is a Drupal Ajax request.
+   *
+   * @const {string}
+   *
+   * @default
+   */
+  Drupal.Ajax.AJAX_REQUEST_PARAMETER = '_drupal_ajax';
+
+  /**
+   * Execute the ajax request.
+   *
+   * Allows developers to execute an Ajax request manually without specifying
+   * an event to respond to.
+   *
+   * @return {object}
+   *   Returns the jQuery.Deferred object underlying the Ajax request. If
+   *   pre-serialization fails, the Deferred will be returned in the rejected
+   *   state.
+   */
+  Drupal.Ajax.prototype.execute = function () {
+    // Do not perform another ajax command if one is already in progress.
+    if (this.ajaxing) {
+      return;
+    }
+
+    try {
+      this.beforeSerialize(this.element, this.options);
+      // Return the jqXHR so that external code can hook into the Deferred API.
+      return $.ajax(this.options);
+    }
+    catch (e) {
+      // Unset the ajax.ajaxing flag here because it won't be unset during
+      // the complete response.
+      this.ajaxing = false;
+      window.alert('An error occurred while attempting to process ' + this.options.url + ': ' + e.message);
+      // For consistency, return a rejected Deferred (i.e., jqXHR's superclass)
+      // so that calling code can take appropriate action.
+      return $.Deferred().reject();
+    }
+  };
+
+  /**
+   * Handle a key press.
+   *
+   * The Ajax object will, if instructed, bind to a key press response. This
+   * will test to see if the key press is valid to trigger this event and
+   * if it is, trigger it for us and prevent other keypresses from triggering.
+   * In this case we're handling RETURN and SPACEBAR keypresses (event codes 13
+   * and 32. RETURN is often used to submit a form when in a textfield, and
+   * SPACE is often used to activate an element without submitting.
+   *
+   * @param {HTMLElement} element
+   *   Element the event was triggered on.
+   * @param {jQuery.Event} event
+   *   Triggered event.
+   */
+  Drupal.Ajax.prototype.keypressResponse = function (element, event) {
+    // Create a synonym for this to reduce code confusion.
+    var ajax = this;
+
+    // Detect enter key and space bar and allow the standard response for them,
+    // except for form elements of type 'text', 'tel', 'number' and 'textarea',
+    // where the spacebar activation causes inappropriate activation if
+    // #ajax['keypress'] is TRUE. On a text-type widget a space should always
+    // be a space.
+    if (event.which === 13 || (event.which === 32 && element.type !== 'text' &&
+      element.type !== 'textarea' && element.type !== 'tel' && element.type !== 'number')) {
+      event.preventDefault();
+      event.stopPropagation();
+      $(ajax.element_settings.element).trigger(ajax.element_settings.event);
+    }
+  };
+
+  /**
+   * Handle an event that triggers an Ajax response.
+   *
+   * When an event that triggers an Ajax response happens, this method will
+   * perform the actual Ajax call. It is bound to the event using
+   * bind() in the constructor, and it uses the options specified on the
+   * Ajax object.
+   *
+   * @param {HTMLElement} element
+   *   Element the event was triggered on.
+   * @param {jQuery.Event} event
+   *   Triggered event.
+   */
+  Drupal.Ajax.prototype.eventResponse = function (element, event) {
+    event.preventDefault();
+    event.stopPropagation();
+
+    // Create a synonym for this to reduce code confusion.
+    var ajax = this;
+
+    // Do not perform another Ajax command if one is already in progress.
+    if (ajax.ajaxing) {
+      return;
+    }
+
+    try {
+      if (ajax.$form) {
+        // If setClick is set, we must set this to ensure that the button's
+        // value is passed.
+        if (ajax.setClick) {
+          // Mark the clicked button. 'form.clk' is a special variable for
+          // ajaxSubmit that tells the system which element got clicked to
+          // trigger the submit. Without it there would be no 'op' or
+          // equivalent.
+          element.form.clk = element;
+        }
+
+        ajax.$form.ajaxSubmit(ajax.options);
+      }
+      else {
+        ajax.beforeSerialize(ajax.element, ajax.options);
+        $.ajax(ajax.options);
+      }
+    }
+    catch (e) {
+      // Unset the ajax.ajaxing flag here because it won't be unset during
+      // the complete response.
+      ajax.ajaxing = false;
+      window.alert('An error occurred while attempting to process ' + ajax.options.url + ': ' + e.message);
+    }
+  };
+
+  /**
+   * Handler for the form serialization.
+   *
+   * Runs before the beforeSend() handler (see below), and unlike that one, runs
+   * before field data is collected.
+   *
+   * @param {object} [element]
+   *   Ajax object's `element_settings`.
+   * @param {object} options
+   *   jQuery.ajax options.
+   */
+  Drupal.Ajax.prototype.beforeSerialize = function (element, options) {
+    // Allow detaching behaviors to update field values before collecting them.
+    // This is only needed when field values are added to the POST data, so only
+    // when there is a form such that this.$form.ajaxSubmit() is used instead of
+    // $.ajax(). When there is no form and $.ajax() is used, beforeSerialize()
+    // isn't called, but don't rely on that: explicitly check this.$form.
+    if (this.$form) {
+      var settings = this.settings || drupalSettings;
+      Drupal.detachBehaviors(this.$form.get(0), settings, 'serialize');
+    }
+
+    // Inform Drupal that this is an AJAX request.
+    options.data[Drupal.Ajax.AJAX_REQUEST_PARAMETER] = 1;
+
+    // Allow Drupal to return new JavaScript and CSS files to load without
+    // returning the ones already loaded.
+    // @see \Drupal\Core\Theme\AjaxBasePageNegotiator
+    // @see \Drupal\Core\Asset\LibraryDependencyResolverInterface::getMinimalRepresentativeSubset()
+    // @see system_js_settings_alter()
+    var pageState = drupalSettings.ajaxPageState;
+    options.data['ajax_page_state[theme]'] = pageState.theme;
+    options.data['ajax_page_state[theme_token]'] = pageState.theme_token;
+    options.data['ajax_page_state[libraries]'] = pageState.libraries;
+  };
+
+  /**
+   * Modify form values prior to form submission.
+   *
+   * @param {Array.<object>} form_values
+   *   Processed form values.
+   * @param {jQuery} element
+   *   The form node as a jQuery object.
+   * @param {object} options
+   *   jQuery.ajax options.
+   */
+  Drupal.Ajax.prototype.beforeSubmit = function (form_values, element, options) {
+    // This function is left empty to make it simple to override for modules
+    // that wish to add functionality here.
+  };
+
+  /**
+   * Prepare the Ajax request before it is sent.
+   *
+   * @param {XMLHttpRequest} xmlhttprequest
+   *   Native Ajax object.
+   * @param {object} options
+   *   jQuery.ajax options.
+   */
+  Drupal.Ajax.prototype.beforeSend = function (xmlhttprequest, options) {
+    // For forms without file inputs, the jQuery Form plugin serializes the
+    // form values, and then calls jQuery's $.ajax() function, which invokes
+    // this handler. In this circumstance, options.extraData is never used. For
+    // forms with file inputs, the jQuery Form plugin uses the browser's normal
+    // form submission mechanism, but captures the response in a hidden IFRAME.
+    // In this circumstance, it calls this handler first, and then appends
+    // hidden fields to the form to submit the values in options.extraData.
+    // There is no simple way to know which submission mechanism will be used,
+    // so we add to extraData regardless, and allow it to be ignored in the
+    // former case.
+    if (this.$form) {
+      options.extraData = options.extraData || {};
+
+      // Let the server know when the IFRAME submission mechanism is used. The
+      // server can use this information to wrap the JSON response in a
+      // TEXTAREA, as per http://jquery.malsup.com/form/#file-upload.
+      options.extraData.ajax_iframe_upload = '1';
+
+      // The triggering element is about to be disabled (see below), but if it
+      // contains a value (e.g., a checkbox, textfield, select, etc.), ensure
+      // that value is included in the submission. As per above, submissions
+      // that use $.ajax() are already serialized prior to the element being
+      // disabled, so this is only needed for IFRAME submissions.
+      var v = $.fieldValue(this.element);
+      if (v !== null) {
+        options.extraData[this.element.name] = v;
+      }
+    }
+
+    // Disable the element that received the change to prevent user interface
+    // interaction while the Ajax request is in progress. ajax.ajaxing prevents
+    // the element from triggering a new request, but does not prevent the user
+    // from changing its value.
+    $(this.element).prop('disabled', true);
+
+    if (!this.progress || !this.progress.type) {
+      return;
+    }
+
+    // Insert progress indicator.
+    var progressIndicatorMethod = 'setProgressIndicator' + this.progress.type.slice(0, 1).toUpperCase() + this.progress.type.slice(1).toLowerCase();
+    if (progressIndicatorMethod in this && typeof this[progressIndicatorMethod] === 'function') {
+      this[progressIndicatorMethod].call(this);
+    }
+  };
+
+  /**
+   * Sets the progress bar progress indicator.
+   */
+  Drupal.Ajax.prototype.setProgressIndicatorBar = function () {
+    var progressBar = new Drupal.ProgressBar('ajax-progress-' + this.element.id, $.noop, this.progress.method, $.noop);
+    if (this.progress.message) {
+      progressBar.setProgress(-1, this.progress.message);
+    }
+    if (this.progress.url) {
+      progressBar.startMonitoring(this.progress.url, this.progress.interval || 1500);
+    }
+    this.progress.element = $(progressBar.element).addClass('ajax-progress ajax-progress-bar');
+    this.progress.object = progressBar;
+    $(this.element).after(this.progress.element);
+  };
+
+  /**
+   * Sets the throbber progress indicator.
+   */
+  Drupal.Ajax.prototype.setProgressIndicatorThrobber = function () {
+    this.progress.element = $('<div class="ajax-progress ajax-progress-throbber"><div class="throbber">&nbsp;</div></div>');
+    if (this.progress.message) {
+      this.progress.element.find('.throbber').after('<div class="message">' + this.progress.message + '</div>');
+    }
+    $(this.element).after(this.progress.element);
+  };
+
+  /**
+   * Sets the fullscreen progress indicator.
+   */
+  Drupal.Ajax.prototype.setProgressIndicatorFullscreen = function () {
+    this.progress.element = $('<div class="ajax-progress ajax-progress-fullscreen">&nbsp;</div>');
+    $('body').after(this.progress.element);
+  };
+
+  /**
+   * Handler for the form redirection completion.
+   *
+   * @param {Array.<Drupal.AjaxCommands~commandDefinition>} response
+   *   Drupal Ajax response.
+   * @param {number} status
+   *   XMLHttpRequest status.
+   */
+  Drupal.Ajax.prototype.success = function (response, status) {
+    // Remove the progress element.
+    if (this.progress.element) {
+      $(this.progress.element).remove();
+    }
+    if (this.progress.object) {
+      this.progress.object.stopMonitoring();
+    }
+    $(this.element).prop('disabled', false);
+
+    // Save element's ancestors tree so if the element is removed from the dom
+    // we can try to refocus one of its parents. Using addBack reverse the
+    // result array, meaning that index 0 is the highest parent in the hierarchy
+    // in this situation it is usually a <form> element.
+    var elementParents = $(this.element).parents('[data-drupal-selector]').addBack().toArray();
+
+    // Track if any command is altering the focus so we can avoid changing the
+    // focus set by the Ajax command.
+    var focusChanged = false;
+    for (var i in response) {
+      if (response.hasOwnProperty(i) && response[i].command && this.commands[response[i].command]) {
+        this.commands[response[i].command](this, response[i], status);
+        if (response[i].command === 'invoke' && response[i].method === 'focus') {
+          focusChanged = true;
+        }
+      }
+    }
+
+    // If the focus hasn't be changed by the ajax commands, try to refocus the
+    // triggering element or one of its parents if that element does not exist
+    // anymore.
+    if (!focusChanged && this.element && !$(this.element).data('disable-refocus')) {
+      var target = false;
+
+      for (var n = elementParents.length - 1; !target && n > 0; n--) {
+        target = document.querySelector('[data-drupal-selector="' + elementParents[n].getAttribute('data-drupal-selector') + '"]');
+      }
+
+      if (target) {
+        $(target).trigger('focus');
+      }
+    }
+
+    // Reattach behaviors, if they were detached in beforeSerialize(). The
+    // attachBehaviors() called on the new content from processing the response
+    // commands is not sufficient, because behaviors from the entire form need
+    // to be reattached.
+    if (this.$form) {
+      var settings = this.settings || drupalSettings;
+      Drupal.attachBehaviors(this.$form.get(0), settings);
+    }
+
+    // Remove any response-specific settings so they don't get used on the next
+    // call by mistake.
+    this.settings = null;
+  };
+
+  /**
+   * Build an effect object to apply an effect when adding new HTML.
+   *
+   * @param {object} response
+   *   Drupal Ajax response.
+   * @param {string} [response.effect]
+   *   Override the default value of {@link Drupal.Ajax#element_settings}.
+   * @param {string|number} [response.speed]
+   *   Override the default value of {@link Drupal.Ajax#element_settings}.
+   *
+   * @return {object}
+   *   Returns an object with `showEffect`, `hideEffect` and `showSpeed`
+   *   properties.
+   */
+  Drupal.Ajax.prototype.getEffect = function (response) {
+    var type = response.effect || this.effect;
+    var speed = response.speed || this.speed;
+
+    var effect = {};
+    if (type === 'none') {
+      effect.showEffect = 'show';
+      effect.hideEffect = 'hide';
+      effect.showSpeed = '';
+    }
+    else if (type === 'fade') {
+      effect.showEffect = 'fadeIn';
+      effect.hideEffect = 'fadeOut';
+      effect.showSpeed = speed;
+    }
+    else {
+      effect.showEffect = type + 'Toggle';
+      effect.hideEffect = type + 'Toggle';
+      effect.showSpeed = speed;
+    }
+
+    return effect;
+  };
+
+  /**
+   * Handler for the form redirection error.
+   *
+   * @param {object} xmlhttprequest
+   *   Native XMLHttpRequest object.
+   * @param {string} uri
+   *   Ajax Request URI.
+   * @param {string} [customMessage]
+   *   Extra message to print with the Ajax error.
+   */
+  Drupal.Ajax.prototype.error = function (xmlhttprequest, uri, customMessage) {
+    // Remove the progress element.
+    if (this.progress.element) {
+      $(this.progress.element).remove();
+    }
+    if (this.progress.object) {
+      this.progress.object.stopMonitoring();
+    }
+    // Undo hide.
+    $(this.wrapper).show();
+    // Re-enable the element.
+    $(this.element).prop('disabled', false);
+    // Reattach behaviors, if they were detached in beforeSerialize().
+    if (this.$form) {
+      var settings = this.settings || drupalSettings;
+      Drupal.attachBehaviors(this.$form.get(0), settings);
+    }
+    throw new Drupal.AjaxError(xmlhttprequest, uri, customMessage);
+  };
+
+  /**
+   * @typedef {object} Drupal.AjaxCommands~commandDefinition
+   *
+   * @prop {string} command
+   * @prop {string} [method]
+   * @prop {string} [selector]
+   * @prop {string} [data]
+   * @prop {object} [settings]
+   * @prop {bool} [asterisk]
+   * @prop {string} [text]
+   * @prop {string} [title]
+   * @prop {string} [url]
+   * @prop {object} [argument]
+   * @prop {string} [name]
+   * @prop {string} [value]
+   * @prop {string} [old]
+   * @prop {string} [new]
+   * @prop {bool} [merge]
+   * @prop {Array} [args]
+   *
+   * @see Drupal.AjaxCommands
+   */
+
+  /**
+   * Provide a series of commands that the client will perform.
+   *
+   * @constructor
+   */
+  Drupal.AjaxCommands = function () {};
+  Drupal.AjaxCommands.prototype = {
+
+    /**
+     * Command to insert new content into the DOM.
+     *
+     * @param {Drupal.Ajax} ajax
+     *   {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
+     * @param {object} response
+     *   The response from the Ajax request.
+     * @param {string} response.data
+     *   The data to use with the jQuery method.
+     * @param {string} [response.method]
+     *   The jQuery DOM manipulation method to be used.
+     * @param {string} [response.selector]
+     *   A optional jQuery selector string.
+     * @param {object} [response.settings]
+     *   An optional array of settings that will be used.
+     * @param {number} [status]
+     *   The XMLHttpRequest status.
+     */
+    insert: function (ajax, response, status) {
+      // Get information from the response. If it is not there, default to
+      // our presets.
+      var $wrapper = response.selector ? $(response.selector) : $(ajax.wrapper);
+      var method = response.method || ajax.method;
+      var effect = ajax.getEffect(response);
+      var settings;
+
+      // We don't know what response.data contains: it might be a string of text
+      // without HTML, so don't rely on jQuery correctly interpreting
+      // $(response.data) as new HTML rather than a CSS selector. Also, if
+      // response.data contains top-level text nodes, they get lost with either
+      // $(response.data) or $('<div></div>').replaceWith(response.data).
+      var $new_content_wrapped = $('<div></div>').html(response.data);
+      var $new_content = $new_content_wrapped.contents();
+
+      // For legacy reasons, the effects processing code assumes that
+      // $new_content consists of a single top-level element. Also, it has not
+      // been sufficiently tested whether attachBehaviors() can be successfully
+      // called with a context object that includes top-level text nodes.
+      // However, to give developers full control of the HTML appearing in the
+      // page, and to enable Ajax content to be inserted in places where <div>
+      // elements are not allowed (e.g., within <table>, <tr>, and <span>
+      // parents), we check if the new content satisfies the requirement
+      // of a single top-level element, and only use the container <div> created
+      // above when it doesn't. For more information, please see
+      // https://www.drupal.org/node/736066.
+      if ($new_content.length !== 1 || $new_content.get(0).nodeType !== 1) {
+        $new_content = $new_content_wrapped;
+      }
+
+      // If removing content from the wrapper, detach behaviors first.
+      switch (method) {
+        case 'html':
+        case 'replaceWith':
+        case 'replaceAll':
+        case 'empty':
+        case 'remove':
+          settings = response.settings || ajax.settings || drupalSettings;
+          Drupal.detachBehaviors($wrapper.get(0), settings);
+      }
+
+      // Add the new content to the page.
+      $wrapper[method]($new_content);
+
+      // Immediately hide the new content if we're using any effects.
+      if (effect.showEffect !== 'show') {
+        $new_content.hide();
+      }
+
+      // Determine which effect to use and what content will receive the
+      // effect, then show the new content.
+      if ($new_content.find('.ajax-new-content').length > 0) {
+        $new_content.find('.ajax-new-content').hide();
+        $new_content.show();
+        $new_content.find('.ajax-new-content')[effect.showEffect](effect.showSpeed);
+      }
+      else if (effect.showEffect !== 'show') {
+        $new_content[effect.showEffect](effect.showSpeed);
+      }
+
+      // Attach all JavaScript behaviors to the new content, if it was
+      // successfully added to the page, this if statement allows
+      // `#ajax['wrapper']` to be optional.
+      if ($new_content.parents('html').length > 0) {
+        // Apply any settings from the returned JSON if available.
+        settings = response.settings || ajax.settings || drupalSettings;
+        Drupal.attachBehaviors($new_content.get(0), settings);
+      }
+    },
+
+    /**
+     * Command to remove a chunk from the page.
+     *
+     * @param {Drupal.Ajax} [ajax]
+     *   {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
+     * @param {object} response
+     *   The response from the Ajax request.
+     * @param {string} response.selector
+     *   A jQuery selector string.
+     * @param {object} [response.settings]
+     *   An optional array of settings that will be used.
+     * @param {number} [status]
+     *   The XMLHttpRequest status.
+     */
+    remove: function (ajax, response, status) {
+      var settings = response.settings || ajax.settings || drupalSettings;
+      $(response.selector).each(function () {
+        Drupal.detachBehaviors(this, settings);
+      })
+        .remove();
+    },
+
+    /**
+     * Command to mark a chunk changed.
+     *
+     * @param {Drupal.Ajax} [ajax]
+     *   {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
+     * @param {object} response
+     *   The JSON response object from the Ajax request.
+     * @param {string} response.selector
+     *   A jQuery selector string.
+     * @param {bool} [response.asterisk]
+     *   An optional CSS selector. If specified, an asterisk will be
+     *   appended to the HTML inside the provided selector.
+     * @param {number} [status]
+     *   The request status.
+     */
+    changed: function (ajax, response, status) {
+      var $element = $(response.selector);
+      if (!$element.hasClass('ajax-changed')) {
+        $element.addClass('ajax-changed');
+        if (response.asterisk) {
+          $element.find(response.asterisk).append(' <abbr class="ajax-changed" title="' + Drupal.t('Changed') + '">*</abbr> ');
+        }
+      }
+    },
+
+    /**
+     * Command to provide an alert.
+     *
+     * @param {Drupal.Ajax} [ajax]
+     *   {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
+     * @param {object} response
+     *   The JSON response from the Ajax request.
+     * @param {string} response.text
+     *   The text that will be displayed in an alert dialog.
+     * @param {number} [status]
+     *   The XMLHttpRequest status.
+     */
+    alert: function (ajax, response, status) {
+      window.alert(response.text, response.title);
+    },
+
+    /**
+     * Command to set the window.location, redirecting the browser.
+     *
+     * @param {Drupal.Ajax} [ajax]
+     *   {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
+     * @param {object} response
+     *   The response from the Ajax request.
+     * @param {string} response.url
+     *   The URL to redirect to.
+     * @param {number} [status]
+     *   The XMLHttpRequest status.
+     */
+    redirect: function (ajax, response, status) {
+      window.location = response.url;
+    },
+
+    /**
+     * Command to provide the jQuery css() function.
+     *
+     * @param {Drupal.Ajax} [ajax]
+     *   {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
+     * @param {object} response
+     *   The response from the Ajax request.
+     * @param {string} response.selector
+     *   A jQuery selector string.
+     * @param {object} response.argument
+     *   An array of key/value pairs to set in the CSS for the selector.
+     * @param {number} [status]
+     *   The XMLHttpRequest status.
+     */
+    css: function (ajax, response, status) {
+      $(response.selector).css(response.argument);
+    },
+
+    /**
+     * Command to set the settings used for other commands in this response.
+     *
+     * This method will also remove expired `drupalSettings.ajax` settings.
+     *
+     * @param {Drupal.Ajax} [ajax]
+     *   {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
+     * @param {object} response
+     *   The response from the Ajax request.
+     * @param {bool} response.merge
+     *   Determines whether the additional settings should be merged to the
+     *   global settings.
+     * @param {object} response.settings
+     *   Contains additional settings to add to the global settings.
+     * @param {number} [status]
+     *   The XMLHttpRequest status.
+     */
+    settings: function (ajax, response, status) {
+      var ajaxSettings = drupalSettings.ajax;
+
+      // Clean up drupalSettings.ajax.
+      if (ajaxSettings) {
+        Drupal.ajax.expired().forEach(function (instance) {
+          // If the Ajax object has been created through drupalSettings.ajax
+          // it will have a selector. When there is no selector the object
+          // has been initialized with a special class name picked up by the
+          // Ajax behavior.
+
+          if (instance.selector) {
+            var selector = instance.selector.replace('#', '');
+            if (selector in ajaxSettings) {
+              delete ajaxSettings[selector];
+            }
+          }
+        });
+      }
+
+      if (response.merge) {
+        $.extend(true, drupalSettings, response.settings);
+      }
+      else {
+        ajax.settings = response.settings;
+      }
+    },
+
+    /**
+     * Command to attach data using jQuery's data API.
+     *
+     * @param {Drupal.Ajax} [ajax]
+     *   {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
+     * @param {object} response
+     *   The response from the Ajax request.
+     * @param {string} response.name
+     *   The name or key (in the key value pair) of the data attached to this
+     *   selector.
+     * @param {string} response.selector
+     *   A jQuery selector string.
+     * @param {string|object} response.value
+     *   The value of to be attached.
+     * @param {number} [status]
+     *   The XMLHttpRequest status.
+     */
+    data: function (ajax, response, status) {
+      $(response.selector).data(response.name, response.value);
+    },
+
+    /**
+     * Command to apply a jQuery method.
+     *
+     * @param {Drupal.Ajax} [ajax]
+     *   {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
+     * @param {object} response
+     *   The response from the Ajax request.
+     * @param {Array} response.args
+     *   An array of arguments to the jQuery method, if any.
+     * @param {string} response.method
+     *   The jQuery method to invoke.
+     * @param {string} response.selector
+     *   A jQuery selector string.
+     * @param {number} [status]
+     *   The XMLHttpRequest status.
+     */
+    invoke: function (ajax, response, status) {
+      var $element = $(response.selector);
+      $element[response.method].apply($element, response.args);
+    },
+
+    /**
+     * Command to restripe a table.
+     *
+     * @param {Drupal.Ajax} [ajax]
+     *   {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
+     * @param {object} response
+     *   The response from the Ajax request.
+     * @param {string} response.selector
+     *   A jQuery selector string.
+     * @param {number} [status]
+     *   The XMLHttpRequest status.
+     */
+    restripe: function (ajax, response, status) {
+      // :even and :odd are reversed because jQuery counts from 0 and
+      // we count from 1, so we're out of sync.
+      // Match immediate children of the parent element to allow nesting.
+      $(response.selector).find('> tbody > tr:visible, > tr:visible')
+        .removeClass('odd even')
+        .filter(':even').addClass('odd').end()
+        .filter(':odd').addClass('even');
+    },
+
+    /**
+     * Command to update a form's build ID.
+     *
+     * @param {Drupal.Ajax} [ajax]
+     *   {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
+     * @param {object} response
+     *   The response from the Ajax request.
+     * @param {string} response.old
+     *   The old form build ID.
+     * @param {string} response.new
+     *   The new form build ID.
+     * @param {number} [status]
+     *   The XMLHttpRequest status.
+     */
+    update_build_id: function (ajax, response, status) {
+      $('input[name="form_build_id"][value="' + response.old + '"]').val(response.new);
+    },
+
+    /**
+     * Command to add css.
+     *
+     * Uses the proprietary addImport method if available as browsers which
+     * support that method ignore @import statements in dynamically added
+     * stylesheets.
+     *
+     * @param {Drupal.Ajax} [ajax]
+     *   {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
+     * @param {object} response
+     *   The response from the Ajax request.
+     * @param {string} response.data
+     *   A string that contains the styles to be added.
+     * @param {number} [status]
+     *   The XMLHttpRequest status.
+     */
+    add_css: function (ajax, response, status) {
+      // Add the styles in the normal way.
+      $('head').prepend(response.data);
+      // Add imports in the styles using the addImport method if available.
+      var match;
+      var importMatch = /^@import url\("(.*)"\);$/igm;
+      if (document.styleSheets[0].addImport && importMatch.test(response.data)) {
+        importMatch.lastIndex = 0;
+        do {
+          match = importMatch.exec(response.data);
+          document.styleSheets[0].addImport(match[1]);
+        } while (match);
+      }
+    }
+  };
+
+})(jQuery, window, Drupal, drupalSettings);
diff --git a/core/misc/ajax.js b/core/misc/ajax.js
index fefe9f3031ef..89b47988bcc0 100644
--- a/core/misc/ajax.js
+++ b/core/misc/ajax.js
@@ -1,35 +1,17 @@
 /**
- * @file
- * Provides Ajax page updating via jQuery $.ajax.
- *
- * Ajax is a method of making a request via JavaScript while viewing an HTML
- * page. The request returns an array of commands encoded in JSON, which is
- * then executed to make any changes that are necessary to the page.
- *
- * Drupal uses this file to enhance form elements with `#ajax['url']` and
- * `#ajax['wrapper']` properties. If set, this file will automatically be
- * included to provide Ajax capabilities.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./misc/ajax.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, window, Drupal, drupalSettings) {
 
   'use strict';
 
-  /**
-   * Attaches the Ajax behavior to each Ajax form element.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Initialize all {@link Drupal.Ajax} objects declared in
-   *   `drupalSettings.ajax` or initialize {@link Drupal.Ajax} objects from
-   *   DOM elements having the `use-ajax-submit` or `use-ajax` css class.
-   * @prop {Drupal~behaviorDetach} detach
-   *   During `unload` remove all {@link Drupal.Ajax} objects related to
-   *   the removed content.
-   */
   Drupal.behaviors.AJAX = {
-    attach: function (context, settings) {
+    attach: function attach(context, settings) {
 
       function loadAjaxBehavior(base) {
         var element_settings = settings.ajax[base];
@@ -43,21 +25,17 @@
         });
       }
 
-      // Load all Ajax behaviors specified in the settings.
       for (var base in settings.ajax) {
         if (settings.ajax.hasOwnProperty(base)) {
           loadAjaxBehavior(base);
         }
       }
 
-      // Bind Ajax behaviors to all items showing the class.
       $('.use-ajax').once('ajax').each(function () {
         var element_settings = {};
-        // Clicked links look better with the throbber than the progress bar.
-        element_settings.progress = {type: 'throbber'};
 
-        // For anchor tags, these will go to the target of the anchor rather
-        // than the usual location.
+        element_settings.progress = { type: 'throbber' };
+
         var href = $(this).attr('href');
         if (href) {
           element_settings.url = href;
@@ -70,21 +48,16 @@
         Drupal.ajax(element_settings);
       });
 
-      // This class means to submit the form to the action using Ajax.
       $('.use-ajax-submit').once('ajax').each(function () {
         var element_settings = {};
 
-        // Ajax submits specified in this manner automatically submit to the
-        // normal form action.
         element_settings.url = $(this.form).attr('action');
-        // Form submit button clicks need to tell the form what was clicked so
-        // it gets passed in the POST request.
+
         element_settings.setClick = true;
-        // Form buttons use the 'click' event rather than mousedown.
+
         element_settings.event = 'click';
-        // Clicked form buttons look better with the throbber than the progress
-        // bar.
-        element_settings.progress = {type: 'throbber'};
+
+        element_settings.progress = { type: 'throbber' };
         element_settings.base = $(this).attr('id');
         element_settings.element = this;
 
@@ -92,31 +65,15 @@
       });
     },
 
-    detach: function (context, settings, trigger) {
+    detach: function detach(context, settings, trigger) {
       if (trigger === 'unload') {
         Drupal.ajax.expired().forEach(function (instance) {
-          // Set this to null and allow garbage collection to reclaim
-          // the memory.
           Drupal.ajax.instances[instance.instanceIndex] = null;
         });
       }
     }
   };
 
-  /**
-   * Extends Error to provide handling for Errors in Ajax.
-   *
-   * @constructor
-   *
-   * @augments Error
-   *
-   * @param {XMLHttpRequest} xmlhttp
-   *   XMLHttpRequest object used for the failed request.
-   * @param {string} uri
-   *   The URI where the error occurred.
-   * @param {string} customMessage
-   *   The custom message.
-   */
   Drupal.AjaxError = function (xmlhttp, uri, customMessage) {
 
     var statusCode;
@@ -125,124 +82,49 @@
     var responseText;
     var readyStateText;
     if (xmlhttp.status) {
-      statusCode = '\n' + Drupal.t('An AJAX HTTP error occurred.') + '\n' + Drupal.t('HTTP Result Code: !status', {'!status': xmlhttp.status});
-    }
-    else {
+      statusCode = '\n' + Drupal.t('An AJAX HTTP error occurred.') + '\n' + Drupal.t('HTTP Result Code: !status', { '!status': xmlhttp.status });
+    } else {
       statusCode = '\n' + Drupal.t('An AJAX HTTP request terminated abnormally.');
     }
     statusCode += '\n' + Drupal.t('Debugging information follows.');
-    pathText = '\n' + Drupal.t('Path: !uri', {'!uri': uri});
+    pathText = '\n' + Drupal.t('Path: !uri', { '!uri': uri });
     statusText = '';
-    // In some cases, when statusCode === 0, xmlhttp.statusText may not be
-    // defined. Unfortunately, testing for it with typeof, etc, doesn't seem to
-    // catch that and the test causes an exception. So we need to catch the
-    // exception here.
+
     try {
-      statusText = '\n' + Drupal.t('StatusText: !statusText', {'!statusText': $.trim(xmlhttp.statusText)});
-    }
-    catch (e) {
-      // Empty.
-    }
+      statusText = '\n' + Drupal.t('StatusText: !statusText', { '!statusText': $.trim(xmlhttp.statusText) });
+    } catch (e) {}
 
     responseText = '';
-    // Again, we don't have a way to know for sure whether accessing
-    // xmlhttp.responseText is going to throw an exception. So we'll catch it.
+
     try {
-      responseText = '\n' + Drupal.t('ResponseText: !responseText', {'!responseText': $.trim(xmlhttp.responseText)});
-    }
-    catch (e) {
-      // Empty.
-    }
+      responseText = '\n' + Drupal.t('ResponseText: !responseText', { '!responseText': $.trim(xmlhttp.responseText) });
+    } catch (e) {}
 
-    // Make the responseText more readable by stripping HTML tags and newlines.
     responseText = responseText.replace(/<("[^"]*"|'[^']*'|[^'">])*>/gi, '');
     responseText = responseText.replace(/[\n]+\s+/g, '\n');
 
-    // We don't need readyState except for status == 0.
-    readyStateText = xmlhttp.status === 0 ? ('\n' + Drupal.t('ReadyState: !readyState', {'!readyState': xmlhttp.readyState})) : '';
+    readyStateText = xmlhttp.status === 0 ? '\n' + Drupal.t('ReadyState: !readyState', { '!readyState': xmlhttp.readyState }) : '';
 
-    customMessage = customMessage ? ('\n' + Drupal.t('CustomMessage: !customMessage', {'!customMessage': customMessage})) : '';
+    customMessage = customMessage ? '\n' + Drupal.t('CustomMessage: !customMessage', { '!customMessage': customMessage }) : '';
 
-    /**
-     * Formatted and translated error message.
-     *
-     * @type {string}
-     */
     this.message = statusCode + pathText + statusText + customMessage + responseText + readyStateText;
 
-    /**
-     * Used by some browsers to display a more accurate stack trace.
-     *
-     * @type {string}
-     */
     this.name = 'AjaxError';
   };
 
   Drupal.AjaxError.prototype = new Error();
   Drupal.AjaxError.prototype.constructor = Drupal.AjaxError;
 
-  /**
-   * Provides Ajax page updating via jQuery $.ajax.
-   *
-   * This function is designed to improve developer experience by wrapping the
-   * initialization of {@link Drupal.Ajax} objects and storing all created
-   * objects in the {@link Drupal.ajax.instances} array.
-   *
-   * @example
-   * Drupal.behaviors.myCustomAJAXStuff = {
-   *   attach: function (context, settings) {
-   *
-   *     var ajaxSettings = {
-   *       url: 'my/url/path',
-   *       // If the old version of Drupal.ajax() needs to be used those
-   *       // properties can be added
-   *       base: 'myBase',
-   *       element: $(context).find('.someElement')
-   *     };
-   *
-   *     var myAjaxObject = Drupal.ajax(ajaxSettings);
-   *
-   *     // Declare a new Ajax command specifically for this Ajax object.
-   *     myAjaxObject.commands.insert = function (ajax, response, status) {
-   *       $('#my-wrapper').append(response.data);
-   *       alert('New content was appended to #my-wrapper');
-   *     };
-   *
-   *     // This command will remove this Ajax object from the page.
-   *     myAjaxObject.commands.destroyObject = function (ajax, response, status) {
-   *       Drupal.ajax.instances[this.instanceIndex] = null;
-   *     };
-   *
-   *     // Programmatically trigger the Ajax request.
-   *     myAjaxObject.execute();
-   *   }
-   * };
-   *
-   * @param {object} settings
-   *   The settings object passed to {@link Drupal.Ajax} constructor.
-   * @param {string} [settings.base]
-   *   Base is passed to {@link Drupal.Ajax} constructor as the 'base'
-   *   parameter.
-   * @param {HTMLElement} [settings.element]
-   *   Element parameter of {@link Drupal.Ajax} constructor, element on which
-   *   event listeners will be bound.
-   *
-   * @return {Drupal.Ajax}
-   *   The created Ajax object.
-   *
-   * @see Drupal.AjaxCommands
-   */
   Drupal.ajax = function (settings) {
     if (arguments.length !== 1) {
       throw new Error('Drupal.ajax() function must be called with one configuration object only');
     }
-    // Map those config keys to variables for the old Drupal.ajax function.
+
     var base = settings.base || false;
     var element = settings.element || false;
     delete settings.base;
     delete settings.element;
 
-    // By default do not display progress for ajax calls without an element.
     if (!settings.progress && !element) {
       settings.progress = false;
     }
@@ -254,88 +136,14 @@
     return ajax;
   };
 
-  /**
-   * Contains all created Ajax objects.
-   *
-   * @type {Array.<Drupal.Ajax|null>}
-   */
   Drupal.ajax.instances = [];
 
-  /**
-   * List all objects where the associated element is not in the DOM
-   *
-   * This method ignores {@link Drupal.Ajax} objects not bound to DOM elements
-   * when created with {@link Drupal.ajax}.
-   *
-   * @return {Array.<Drupal.Ajax>}
-   *   The list of expired {@link Drupal.Ajax} objects.
-   */
   Drupal.ajax.expired = function () {
     return Drupal.ajax.instances.filter(function (instance) {
       return instance && instance.element !== false && !document.body.contains(instance.element);
     });
   };
 
-  /**
-   * Settings for an Ajax object.
-   *
-   * @typedef {object} Drupal.Ajax~element_settings
-   *
-   * @prop {string} url
-   *   Target of the Ajax request.
-   * @prop {?string} [event]
-   *   Event bound to settings.element which will trigger the Ajax request.
-   * @prop {bool} [keypress=true]
-   *   Triggers a request on keypress events.
-   * @prop {?string} selector
-   *   jQuery selector targeting the element to bind events to or used with
-   *   {@link Drupal.AjaxCommands}.
-   * @prop {string} [effect='none']
-   *   Name of the jQuery method to use for displaying new Ajax content.
-   * @prop {string|number} [speed='none']
-   *   Speed with which to apply the effect.
-   * @prop {string} [method]
-   *   Name of the jQuery method used to insert new content in the targeted
-   *   element.
-   * @prop {object} [progress]
-   *   Settings for the display of a user-friendly loader.
-   * @prop {string} [progress.type='throbber']
-   *   Type of progress element, core provides `'bar'`, `'throbber'` and
-   *   `'fullscreen'`.
-   * @prop {string} [progress.message=Drupal.t('Please wait...')]
-   *   Custom message to be used with the bar indicator.
-   * @prop {object} [submit]
-   *   Extra data to be sent with the Ajax request.
-   * @prop {bool} [submit.js=true]
-   *   Allows the PHP side to know this comes from an Ajax request.
-   * @prop {object} [dialog]
-   *   Options for {@link Drupal.dialog}.
-   * @prop {string} [dialogType]
-   *   One of `'modal'` or `'dialog'`.
-   * @prop {string} [prevent]
-   *   List of events on which to stop default action and stop propagation.
-   */
-
-  /**
-   * Ajax constructor.
-   *
-   * The Ajax request returns an array of commands encoded in JSON, which is
-   * then executed to make any changes that are necessary to the page.
-   *
-   * Drupal uses this file to enhance form elements with `#ajax['url']` and
-   * `#ajax['wrapper']` properties. If set, this file will automatically be
-   * included to provide Ajax capabilities.
-   *
-   * @constructor
-   *
-   * @param {string} [base]
-   *   Base parameter of {@link Drupal.Ajax} constructor
-   * @param {HTMLElement} [element]
-   *   Element parameter of {@link Drupal.Ajax} constructor, element on which
-   *   event listeners will be bound.
-   * @param {Drupal.Ajax~element_settings} element_settings
-   *   Settings for this Ajax object.
-   */
   Drupal.Ajax = function (base, element, element_settings) {
     var defaults = {
       event: element ? 'mousedown' : null,
@@ -355,146 +163,60 @@
 
     $.extend(this, defaults, element_settings);
 
-    /**
-     * @type {Drupal.AjaxCommands}
-     */
     this.commands = new Drupal.AjaxCommands();
 
-    /**
-     * @type {bool|number}
-     */
     this.instanceIndex = false;
 
-    // @todo Remove this after refactoring the PHP code to:
-    //   - Call this 'selector'.
-    //   - Include the '#' for ID-based selectors.
-    //   - Support non-ID-based selectors.
     if (this.wrapper) {
-
-      /**
-       * @type {string}
-       */
       this.wrapper = '#' + this.wrapper;
     }
 
-    /**
-     * @type {HTMLElement}
-     */
     this.element = element;
 
-    /**
-     * @type {Drupal.Ajax~element_settings}
-     */
     this.element_settings = element_settings;
 
-    // If there isn't a form, jQuery.ajax() will be used instead, allowing us to
-    // bind Ajax to links as well.
     if (this.element && this.element.form) {
-
-      /**
-       * @type {jQuery}
-       */
       this.$form = $(this.element.form);
     }
 
-    // If no Ajax callback URL was given, use the link href or form action.
     if (!this.url) {
       var $element = $(this.element);
       if ($element.is('a')) {
         this.url = $element.attr('href');
-      }
-      else if (this.element && element.form) {
+      } else if (this.element && element.form) {
         this.url = this.$form.attr('action');
       }
     }
 
-    // Replacing 'nojs' with 'ajax' in the URL allows for an easy method to let
-    // the server detect when it needs to degrade gracefully.
-    // There are four scenarios to check for:
-    // 1. /nojs/
-    // 2. /nojs$ - The end of a URL string.
-    // 3. /nojs? - Followed by a query (e.g. path/nojs?destination=foobar).
-    // 4. /nojs# - Followed by a fragment (e.g.: path/nojs#myfragment).
     var originalUrl = this.url;
 
-    /**
-     * Processed Ajax URL.
-     *
-     * @type {string}
-     */
     this.url = this.url.replace(/\/nojs(\/|$|\?|#)/g, '/ajax$1');
-    // If the 'nojs' version of the URL is trusted, also trust the 'ajax'
-    // version.
+
     if (drupalSettings.ajaxTrustedUrl[originalUrl]) {
       drupalSettings.ajaxTrustedUrl[this.url] = true;
     }
 
-    // Set the options for the ajaxSubmit function.
-    // The 'this' variable will not persist inside of the options object.
     var ajax = this;
 
-    /**
-     * Options for the jQuery.ajax function.
-     *
-     * @name Drupal.Ajax#options
-     *
-     * @type {object}
-     *
-     * @prop {string} url
-     *   Ajax URL to be called.
-     * @prop {object} data
-     *   Ajax payload.
-     * @prop {function} beforeSerialize
-     *   Implement jQuery beforeSerialize function to call
-     *   {@link Drupal.Ajax#beforeSerialize}.
-     * @prop {function} beforeSubmit
-     *   Implement jQuery beforeSubmit function to call
-     *   {@link Drupal.Ajax#beforeSubmit}.
-     * @prop {function} beforeSend
-     *   Implement jQuery beforeSend function to call
-     *   {@link Drupal.Ajax#beforeSend}.
-     * @prop {function} success
-     *   Implement jQuery success function to call
-     *   {@link Drupal.Ajax#success}.
-     * @prop {function} complete
-     *   Implement jQuery success function to clean up ajax state and trigger an
-     *   error if needed.
-     * @prop {string} dataType='json'
-     *   Type of the response expected.
-     * @prop {string} type='POST'
-     *   HTTP method to use for the Ajax request.
-     */
     ajax.options = {
       url: ajax.url,
       data: ajax.submit,
-      beforeSerialize: function (element_settings, options) {
+      beforeSerialize: function beforeSerialize(element_settings, options) {
         return ajax.beforeSerialize(element_settings, options);
       },
-      beforeSubmit: function (form_values, element_settings, options) {
+      beforeSubmit: function beforeSubmit(form_values, element_settings, options) {
         ajax.ajaxing = true;
         return ajax.beforeSubmit(form_values, element_settings, options);
       },
-      beforeSend: function (xmlhttprequest, options) {
+      beforeSend: function beforeSend(xmlhttprequest, options) {
         ajax.ajaxing = true;
         return ajax.beforeSend(xmlhttprequest, options);
       },
-      success: function (response, status, xmlhttprequest) {
-        // Sanity check for browser support (object expected).
-        // When using iFrame uploads, responses must be returned as a string.
+      success: function success(response, status, xmlhttprequest) {
         if (typeof response === 'string') {
           response = $.parseJSON(response);
         }
 
-        // Prior to invoking the response's commands, verify that they can be
-        // trusted by checking for a response header. See
-        // \Drupal\Core\EventSubscriber\AjaxResponseSubscriber for details.
-        // - Empty responses are harmless so can bypass verification. This
-        //   avoids an alert message for server-generated no-op responses that
-        //   skip Ajax rendering.
-        // - Ajax objects with trusted URLs (e.g., ones defined server-side via
-        //   #ajax) can bypass header verification. This is especially useful
-        //   for Ajax with multipart forms. Because IFRAME transport is used,
-        //   the response headers cannot be accessed for verification.
         if (response !== null && !drupalSettings.ajaxTrustedUrl[ajax.url]) {
           if (xmlhttprequest.getResponseHeader('X-Drupal-Ajax-Token') !== '1') {
             var customMessage = Drupal.t('The response failed verification so will not be processed.');
@@ -504,7 +226,7 @@
 
         return ajax.success(response, status);
       },
-      complete: function (xmlhttprequest, status) {
+      complete: function complete(xmlhttprequest, status) {
         ajax.ajaxing = false;
         if (status === 'error' || status === 'parsererror') {
           return ajax.error(xmlhttprequest, ajax.url);
@@ -518,288 +240,129 @@
       ajax.options.data.dialogOptions = element_settings.dialog;
     }
 
-    // Ensure that we have a valid URL by adding ? when no query parameter is
-    // yet available, otherwise append using &.
     if (ajax.options.url.indexOf('?') === -1) {
       ajax.options.url += '?';
-    }
-    else {
+    } else {
       ajax.options.url += '&';
     }
     ajax.options.url += Drupal.ajax.WRAPPER_FORMAT + '=drupal_' + (element_settings.dialogType || 'ajax');
 
-    // Bind the ajaxSubmit function to the element event.
     $(ajax.element).on(element_settings.event, function (event) {
       if (!drupalSettings.ajaxTrustedUrl[ajax.url] && !Drupal.url.isLocal(ajax.url)) {
-        throw new Error(Drupal.t('The callback URL is not local and not trusted: !url', {'!url': ajax.url}));
+        throw new Error(Drupal.t('The callback URL is not local and not trusted: !url', { '!url': ajax.url }));
       }
       return ajax.eventResponse(this, event);
     });
 
-    // If necessary, enable keyboard submission so that Ajax behaviors
-    // can be triggered through keyboard input as well as e.g. a mousedown
-    // action.
     if (element_settings.keypress) {
       $(ajax.element).on('keypress', function (event) {
         return ajax.keypressResponse(this, event);
       });
     }
 
-    // If necessary, prevent the browser default action of an additional event.
-    // For example, prevent the browser default action of a click, even if the
-    // Ajax behavior binds to mousedown.
     if (element_settings.prevent) {
       $(ajax.element).on(element_settings.prevent, false);
     }
   };
 
-  /**
-   * URL query attribute to indicate the wrapper used to render a request.
-   *
-   * The wrapper format determines how the HTML is wrapped, for example in a
-   * modal dialog.
-   *
-   * @const {string}
-   *
-   * @default
-   */
   Drupal.ajax.WRAPPER_FORMAT = '_wrapper_format';
 
-  /**
-   * Request parameter to indicate that a request is a Drupal Ajax request.
-   *
-   * @const {string}
-   *
-   * @default
-   */
   Drupal.Ajax.AJAX_REQUEST_PARAMETER = '_drupal_ajax';
 
-  /**
-   * Execute the ajax request.
-   *
-   * Allows developers to execute an Ajax request manually without specifying
-   * an event to respond to.
-   *
-   * @return {object}
-   *   Returns the jQuery.Deferred object underlying the Ajax request. If
-   *   pre-serialization fails, the Deferred will be returned in the rejected
-   *   state.
-   */
   Drupal.Ajax.prototype.execute = function () {
-    // Do not perform another ajax command if one is already in progress.
     if (this.ajaxing) {
       return;
     }
 
     try {
       this.beforeSerialize(this.element, this.options);
-      // Return the jqXHR so that external code can hook into the Deferred API.
+
       return $.ajax(this.options);
-    }
-    catch (e) {
-      // Unset the ajax.ajaxing flag here because it won't be unset during
-      // the complete response.
+    } catch (e) {
       this.ajaxing = false;
       window.alert('An error occurred while attempting to process ' + this.options.url + ': ' + e.message);
-      // For consistency, return a rejected Deferred (i.e., jqXHR's superclass)
-      // so that calling code can take appropriate action.
+
       return $.Deferred().reject();
     }
   };
 
-  /**
-   * Handle a key press.
-   *
-   * The Ajax object will, if instructed, bind to a key press response. This
-   * will test to see if the key press is valid to trigger this event and
-   * if it is, trigger it for us and prevent other keypresses from triggering.
-   * In this case we're handling RETURN and SPACEBAR keypresses (event codes 13
-   * and 32. RETURN is often used to submit a form when in a textfield, and
-   * SPACE is often used to activate an element without submitting.
-   *
-   * @param {HTMLElement} element
-   *   Element the event was triggered on.
-   * @param {jQuery.Event} event
-   *   Triggered event.
-   */
   Drupal.Ajax.prototype.keypressResponse = function (element, event) {
-    // Create a synonym for this to reduce code confusion.
     var ajax = this;
 
-    // Detect enter key and space bar and allow the standard response for them,
-    // except for form elements of type 'text', 'tel', 'number' and 'textarea',
-    // where the spacebar activation causes inappropriate activation if
-    // #ajax['keypress'] is TRUE. On a text-type widget a space should always
-    // be a space.
-    if (event.which === 13 || (event.which === 32 && element.type !== 'text' &&
-      element.type !== 'textarea' && element.type !== 'tel' && element.type !== 'number')) {
+    if (event.which === 13 || event.which === 32 && element.type !== 'text' && element.type !== 'textarea' && element.type !== 'tel' && element.type !== 'number') {
       event.preventDefault();
       event.stopPropagation();
       $(ajax.element_settings.element).trigger(ajax.element_settings.event);
     }
   };
 
-  /**
-   * Handle an event that triggers an Ajax response.
-   *
-   * When an event that triggers an Ajax response happens, this method will
-   * perform the actual Ajax call. It is bound to the event using
-   * bind() in the constructor, and it uses the options specified on the
-   * Ajax object.
-   *
-   * @param {HTMLElement} element
-   *   Element the event was triggered on.
-   * @param {jQuery.Event} event
-   *   Triggered event.
-   */
   Drupal.Ajax.prototype.eventResponse = function (element, event) {
     event.preventDefault();
     event.stopPropagation();
 
-    // Create a synonym for this to reduce code confusion.
     var ajax = this;
 
-    // Do not perform another Ajax command if one is already in progress.
     if (ajax.ajaxing) {
       return;
     }
 
     try {
       if (ajax.$form) {
-        // If setClick is set, we must set this to ensure that the button's
-        // value is passed.
         if (ajax.setClick) {
-          // Mark the clicked button. 'form.clk' is a special variable for
-          // ajaxSubmit that tells the system which element got clicked to
-          // trigger the submit. Without it there would be no 'op' or
-          // equivalent.
           element.form.clk = element;
         }
 
         ajax.$form.ajaxSubmit(ajax.options);
-      }
-      else {
+      } else {
         ajax.beforeSerialize(ajax.element, ajax.options);
         $.ajax(ajax.options);
       }
-    }
-    catch (e) {
-      // Unset the ajax.ajaxing flag here because it won't be unset during
-      // the complete response.
+    } catch (e) {
       ajax.ajaxing = false;
       window.alert('An error occurred while attempting to process ' + ajax.options.url + ': ' + e.message);
     }
   };
 
-  /**
-   * Handler for the form serialization.
-   *
-   * Runs before the beforeSend() handler (see below), and unlike that one, runs
-   * before field data is collected.
-   *
-   * @param {object} [element]
-   *   Ajax object's `element_settings`.
-   * @param {object} options
-   *   jQuery.ajax options.
-   */
   Drupal.Ajax.prototype.beforeSerialize = function (element, options) {
-    // Allow detaching behaviors to update field values before collecting them.
-    // This is only needed when field values are added to the POST data, so only
-    // when there is a form such that this.$form.ajaxSubmit() is used instead of
-    // $.ajax(). When there is no form and $.ajax() is used, beforeSerialize()
-    // isn't called, but don't rely on that: explicitly check this.$form.
     if (this.$form) {
       var settings = this.settings || drupalSettings;
       Drupal.detachBehaviors(this.$form.get(0), settings, 'serialize');
     }
 
-    // Inform Drupal that this is an AJAX request.
     options.data[Drupal.Ajax.AJAX_REQUEST_PARAMETER] = 1;
 
-    // Allow Drupal to return new JavaScript and CSS files to load without
-    // returning the ones already loaded.
-    // @see \Drupal\Core\Theme\AjaxBasePageNegotiator
-    // @see \Drupal\Core\Asset\LibraryDependencyResolverInterface::getMinimalRepresentativeSubset()
-    // @see system_js_settings_alter()
     var pageState = drupalSettings.ajaxPageState;
     options.data['ajax_page_state[theme]'] = pageState.theme;
     options.data['ajax_page_state[theme_token]'] = pageState.theme_token;
     options.data['ajax_page_state[libraries]'] = pageState.libraries;
   };
 
-  /**
-   * Modify form values prior to form submission.
-   *
-   * @param {Array.<object>} form_values
-   *   Processed form values.
-   * @param {jQuery} element
-   *   The form node as a jQuery object.
-   * @param {object} options
-   *   jQuery.ajax options.
-   */
-  Drupal.Ajax.prototype.beforeSubmit = function (form_values, element, options) {
-    // This function is left empty to make it simple to override for modules
-    // that wish to add functionality here.
-  };
+  Drupal.Ajax.prototype.beforeSubmit = function (form_values, element, options) {};
 
-  /**
-   * Prepare the Ajax request before it is sent.
-   *
-   * @param {XMLHttpRequest} xmlhttprequest
-   *   Native Ajax object.
-   * @param {object} options
-   *   jQuery.ajax options.
-   */
   Drupal.Ajax.prototype.beforeSend = function (xmlhttprequest, options) {
-    // For forms without file inputs, the jQuery Form plugin serializes the
-    // form values, and then calls jQuery's $.ajax() function, which invokes
-    // this handler. In this circumstance, options.extraData is never used. For
-    // forms with file inputs, the jQuery Form plugin uses the browser's normal
-    // form submission mechanism, but captures the response in a hidden IFRAME.
-    // In this circumstance, it calls this handler first, and then appends
-    // hidden fields to the form to submit the values in options.extraData.
-    // There is no simple way to know which submission mechanism will be used,
-    // so we add to extraData regardless, and allow it to be ignored in the
-    // former case.
     if (this.$form) {
       options.extraData = options.extraData || {};
 
-      // Let the server know when the IFRAME submission mechanism is used. The
-      // server can use this information to wrap the JSON response in a
-      // TEXTAREA, as per http://jquery.malsup.com/form/#file-upload.
       options.extraData.ajax_iframe_upload = '1';
 
-      // The triggering element is about to be disabled (see below), but if it
-      // contains a value (e.g., a checkbox, textfield, select, etc.), ensure
-      // that value is included in the submission. As per above, submissions
-      // that use $.ajax() are already serialized prior to the element being
-      // disabled, so this is only needed for IFRAME submissions.
       var v = $.fieldValue(this.element);
       if (v !== null) {
         options.extraData[this.element.name] = v;
       }
     }
 
-    // Disable the element that received the change to prevent user interface
-    // interaction while the Ajax request is in progress. ajax.ajaxing prevents
-    // the element from triggering a new request, but does not prevent the user
-    // from changing its value.
     $(this.element).prop('disabled', true);
 
     if (!this.progress || !this.progress.type) {
       return;
     }
 
-    // Insert progress indicator.
     var progressIndicatorMethod = 'setProgressIndicator' + this.progress.type.slice(0, 1).toUpperCase() + this.progress.type.slice(1).toLowerCase();
     if (progressIndicatorMethod in this && typeof this[progressIndicatorMethod] === 'function') {
       this[progressIndicatorMethod].call(this);
     }
   };
 
-  /**
-   * Sets the progress bar progress indicator.
-   */
   Drupal.Ajax.prototype.setProgressIndicatorBar = function () {
     var progressBar = new Drupal.ProgressBar('ajax-progress-' + this.element.id, $.noop, this.progress.method, $.noop);
     if (this.progress.message) {
@@ -813,9 +376,6 @@
     $(this.element).after(this.progress.element);
   };
 
-  /**
-   * Sets the throbber progress indicator.
-   */
   Drupal.Ajax.prototype.setProgressIndicatorThrobber = function () {
     this.progress.element = $('<div class="ajax-progress ajax-progress-throbber"><div class="throbber">&nbsp;</div></div>');
     if (this.progress.message) {
@@ -824,24 +384,12 @@
     $(this.element).after(this.progress.element);
   };
 
-  /**
-   * Sets the fullscreen progress indicator.
-   */
   Drupal.Ajax.prototype.setProgressIndicatorFullscreen = function () {
     this.progress.element = $('<div class="ajax-progress ajax-progress-fullscreen">&nbsp;</div>');
     $('body').after(this.progress.element);
   };
 
-  /**
-   * Handler for the form redirection completion.
-   *
-   * @param {Array.<Drupal.AjaxCommands~commandDefinition>} response
-   *   Drupal Ajax response.
-   * @param {number} status
-   *   XMLHttpRequest status.
-   */
   Drupal.Ajax.prototype.success = function (response, status) {
-    // Remove the progress element.
     if (this.progress.element) {
       $(this.progress.element).remove();
     }
@@ -850,14 +398,8 @@
     }
     $(this.element).prop('disabled', false);
 
-    // Save element's ancestors tree so if the element is removed from the dom
-    // we can try to refocus one of its parents. Using addBack reverse the
-    // result array, meaning that index 0 is the highest parent in the hierarchy
-    // in this situation it is usually a <form> element.
     var elementParents = $(this.element).parents('[data-drupal-selector]').addBack().toArray();
 
-    // Track if any command is altering the focus so we can avoid changing the
-    // focus set by the Ajax command.
     var focusChanged = false;
     for (var i in response) {
       if (response.hasOwnProperty(i) && response[i].command && this.commands[response[i].command]) {
@@ -868,9 +410,6 @@
       }
     }
 
-    // If the focus hasn't be changed by the ajax commands, try to refocus the
-    // triggering element or one of its parents if that element does not exist
-    // anymore.
     if (!focusChanged && this.element && !$(this.element).data('disable-refocus')) {
       var target = false;
 
@@ -883,34 +422,14 @@
       }
     }
 
-    // Reattach behaviors, if they were detached in beforeSerialize(). The
-    // attachBehaviors() called on the new content from processing the response
-    // commands is not sufficient, because behaviors from the entire form need
-    // to be reattached.
     if (this.$form) {
       var settings = this.settings || drupalSettings;
       Drupal.attachBehaviors(this.$form.get(0), settings);
     }
 
-    // Remove any response-specific settings so they don't get used on the next
-    // call by mistake.
     this.settings = null;
   };
 
-  /**
-   * Build an effect object to apply an effect when adding new HTML.
-   *
-   * @param {object} response
-   *   Drupal Ajax response.
-   * @param {string} [response.effect]
-   *   Override the default value of {@link Drupal.Ajax#element_settings}.
-   * @param {string|number} [response.speed]
-   *   Override the default value of {@link Drupal.Ajax#element_settings}.
-   *
-   * @return {object}
-   *   Returns an object with `showEffect`, `hideEffect` and `showSpeed`
-   *   properties.
-   */
   Drupal.Ajax.prototype.getEffect = function (response) {
     var type = response.effect || this.effect;
     var speed = response.speed || this.speed;
@@ -920,13 +439,11 @@
       effect.showEffect = 'show';
       effect.hideEffect = 'hide';
       effect.showSpeed = '';
-    }
-    else if (type === 'fade') {
+    } else if (type === 'fade') {
       effect.showEffect = 'fadeIn';
       effect.hideEffect = 'fadeOut';
       effect.showSpeed = speed;
-    }
-    else {
+    } else {
       effect.showEffect = type + 'Toggle';
       effect.hideEffect = type + 'Toggle';
       effect.showSpeed = speed;
@@ -935,29 +452,18 @@
     return effect;
   };
 
-  /**
-   * Handler for the form redirection error.
-   *
-   * @param {object} xmlhttprequest
-   *   Native XMLHttpRequest object.
-   * @param {string} uri
-   *   Ajax Request URI.
-   * @param {string} [customMessage]
-   *   Extra message to print with the Ajax error.
-   */
   Drupal.Ajax.prototype.error = function (xmlhttprequest, uri, customMessage) {
-    // Remove the progress element.
     if (this.progress.element) {
       $(this.progress.element).remove();
     }
     if (this.progress.object) {
       this.progress.object.stopMonitoring();
     }
-    // Undo hide.
+
     $(this.wrapper).show();
-    // Re-enable the element.
+
     $(this.element).prop('disabled', false);
-    // Reattach behaviors, if they were detached in beforeSerialize().
+
     if (this.$form) {
       var settings = this.settings || drupalSettings;
       Drupal.attachBehaviors(this.$form.get(0), settings);
@@ -965,87 +471,21 @@
     throw new Drupal.AjaxError(xmlhttprequest, uri, customMessage);
   };
 
-  /**
-   * @typedef {object} Drupal.AjaxCommands~commandDefinition
-   *
-   * @prop {string} command
-   * @prop {string} [method]
-   * @prop {string} [selector]
-   * @prop {string} [data]
-   * @prop {object} [settings]
-   * @prop {bool} [asterisk]
-   * @prop {string} [text]
-   * @prop {string} [title]
-   * @prop {string} [url]
-   * @prop {object} [argument]
-   * @prop {string} [name]
-   * @prop {string} [value]
-   * @prop {string} [old]
-   * @prop {string} [new]
-   * @prop {bool} [merge]
-   * @prop {Array} [args]
-   *
-   * @see Drupal.AjaxCommands
-   */
-
-  /**
-   * Provide a series of commands that the client will perform.
-   *
-   * @constructor
-   */
   Drupal.AjaxCommands = function () {};
   Drupal.AjaxCommands.prototype = {
-
-    /**
-     * Command to insert new content into the DOM.
-     *
-     * @param {Drupal.Ajax} ajax
-     *   {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
-     * @param {object} response
-     *   The response from the Ajax request.
-     * @param {string} response.data
-     *   The data to use with the jQuery method.
-     * @param {string} [response.method]
-     *   The jQuery DOM manipulation method to be used.
-     * @param {string} [response.selector]
-     *   A optional jQuery selector string.
-     * @param {object} [response.settings]
-     *   An optional array of settings that will be used.
-     * @param {number} [status]
-     *   The XMLHttpRequest status.
-     */
-    insert: function (ajax, response, status) {
-      // Get information from the response. If it is not there, default to
-      // our presets.
+    insert: function insert(ajax, response, status) {
       var $wrapper = response.selector ? $(response.selector) : $(ajax.wrapper);
       var method = response.method || ajax.method;
       var effect = ajax.getEffect(response);
       var settings;
 
-      // We don't know what response.data contains: it might be a string of text
-      // without HTML, so don't rely on jQuery correctly interpreting
-      // $(response.data) as new HTML rather than a CSS selector. Also, if
-      // response.data contains top-level text nodes, they get lost with either
-      // $(response.data) or $('<div></div>').replaceWith(response.data).
       var $new_content_wrapped = $('<div></div>').html(response.data);
       var $new_content = $new_content_wrapped.contents();
 
-      // For legacy reasons, the effects processing code assumes that
-      // $new_content consists of a single top-level element. Also, it has not
-      // been sufficiently tested whether attachBehaviors() can be successfully
-      // called with a context object that includes top-level text nodes.
-      // However, to give developers full control of the HTML appearing in the
-      // page, and to enable Ajax content to be inserted in places where <div>
-      // elements are not allowed (e.g., within <table>, <tr>, and <span>
-      // parents), we check if the new content satisfies the requirement
-      // of a single top-level element, and only use the container <div> created
-      // above when it doesn't. For more information, please see
-      // https://www.drupal.org/node/736066.
       if ($new_content.length !== 1 || $new_content.get(0).nodeType !== 1) {
         $new_content = $new_content_wrapped;
       }
 
-      // If removing content from the wrapper, detach behaviors first.
       switch (method) {
         case 'html':
         case 'replaceWith':
@@ -1056,73 +496,34 @@
           Drupal.detachBehaviors($wrapper.get(0), settings);
       }
 
-      // Add the new content to the page.
       $wrapper[method]($new_content);
 
-      // Immediately hide the new content if we're using any effects.
       if (effect.showEffect !== 'show') {
         $new_content.hide();
       }
 
-      // Determine which effect to use and what content will receive the
-      // effect, then show the new content.
       if ($new_content.find('.ajax-new-content').length > 0) {
         $new_content.find('.ajax-new-content').hide();
         $new_content.show();
         $new_content.find('.ajax-new-content')[effect.showEffect](effect.showSpeed);
-      }
-      else if (effect.showEffect !== 'show') {
+      } else if (effect.showEffect !== 'show') {
         $new_content[effect.showEffect](effect.showSpeed);
       }
 
-      // Attach all JavaScript behaviors to the new content, if it was
-      // successfully added to the page, this if statement allows
-      // `#ajax['wrapper']` to be optional.
       if ($new_content.parents('html').length > 0) {
-        // Apply any settings from the returned JSON if available.
         settings = response.settings || ajax.settings || drupalSettings;
         Drupal.attachBehaviors($new_content.get(0), settings);
       }
     },
 
-    /**
-     * Command to remove a chunk from the page.
-     *
-     * @param {Drupal.Ajax} [ajax]
-     *   {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
-     * @param {object} response
-     *   The response from the Ajax request.
-     * @param {string} response.selector
-     *   A jQuery selector string.
-     * @param {object} [response.settings]
-     *   An optional array of settings that will be used.
-     * @param {number} [status]
-     *   The XMLHttpRequest status.
-     */
-    remove: function (ajax, response, status) {
+    remove: function remove(ajax, response, status) {
       var settings = response.settings || ajax.settings || drupalSettings;
       $(response.selector).each(function () {
         Drupal.detachBehaviors(this, settings);
-      })
-        .remove();
+      }).remove();
     },
 
-    /**
-     * Command to mark a chunk changed.
-     *
-     * @param {Drupal.Ajax} [ajax]
-     *   {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
-     * @param {object} response
-     *   The JSON response object from the Ajax request.
-     * @param {string} response.selector
-     *   A jQuery selector string.
-     * @param {bool} [response.asterisk]
-     *   An optional CSS selector. If specified, an asterisk will be
-     *   appended to the HTML inside the provided selector.
-     * @param {number} [status]
-     *   The request status.
-     */
-    changed: function (ajax, response, status) {
+    changed: function changed(ajax, response, status) {
       var $element = $(response.selector);
       if (!$element.hasClass('ajax-changed')) {
         $element.addClass('ajax-changed');
@@ -1132,83 +533,23 @@
       }
     },
 
-    /**
-     * Command to provide an alert.
-     *
-     * @param {Drupal.Ajax} [ajax]
-     *   {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
-     * @param {object} response
-     *   The JSON response from the Ajax request.
-     * @param {string} response.text
-     *   The text that will be displayed in an alert dialog.
-     * @param {number} [status]
-     *   The XMLHttpRequest status.
-     */
-    alert: function (ajax, response, status) {
+    alert: function alert(ajax, response, status) {
       window.alert(response.text, response.title);
     },
 
-    /**
-     * Command to set the window.location, redirecting the browser.
-     *
-     * @param {Drupal.Ajax} [ajax]
-     *   {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
-     * @param {object} response
-     *   The response from the Ajax request.
-     * @param {string} response.url
-     *   The URL to redirect to.
-     * @param {number} [status]
-     *   The XMLHttpRequest status.
-     */
-    redirect: function (ajax, response, status) {
+    redirect: function redirect(ajax, response, status) {
       window.location = response.url;
     },
 
-    /**
-     * Command to provide the jQuery css() function.
-     *
-     * @param {Drupal.Ajax} [ajax]
-     *   {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
-     * @param {object} response
-     *   The response from the Ajax request.
-     * @param {string} response.selector
-     *   A jQuery selector string.
-     * @param {object} response.argument
-     *   An array of key/value pairs to set in the CSS for the selector.
-     * @param {number} [status]
-     *   The XMLHttpRequest status.
-     */
-    css: function (ajax, response, status) {
+    css: function css(ajax, response, status) {
       $(response.selector).css(response.argument);
     },
 
-    /**
-     * Command to set the settings used for other commands in this response.
-     *
-     * This method will also remove expired `drupalSettings.ajax` settings.
-     *
-     * @param {Drupal.Ajax} [ajax]
-     *   {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
-     * @param {object} response
-     *   The response from the Ajax request.
-     * @param {bool} response.merge
-     *   Determines whether the additional settings should be merged to the
-     *   global settings.
-     * @param {object} response.settings
-     *   Contains additional settings to add to the global settings.
-     * @param {number} [status]
-     *   The XMLHttpRequest status.
-     */
-    settings: function (ajax, response, status) {
+    settings: function settings(ajax, response, status) {
       var ajaxSettings = drupalSettings.ajax;
 
-      // Clean up drupalSettings.ajax.
       if (ajaxSettings) {
         Drupal.ajax.expired().forEach(function (instance) {
-          // If the Ajax object has been created through drupalSettings.ajax
-          // it will have a selector. When there is no selector the object
-          // has been initialized with a special class name picked up by the
-          // Ajax behavior.
 
           if (instance.selector) {
             var selector = instance.selector.replace('#', '');
@@ -1221,114 +562,31 @@
 
       if (response.merge) {
         $.extend(true, drupalSettings, response.settings);
-      }
-      else {
+      } else {
         ajax.settings = response.settings;
       }
     },
 
-    /**
-     * Command to attach data using jQuery's data API.
-     *
-     * @param {Drupal.Ajax} [ajax]
-     *   {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
-     * @param {object} response
-     *   The response from the Ajax request.
-     * @param {string} response.name
-     *   The name or key (in the key value pair) of the data attached to this
-     *   selector.
-     * @param {string} response.selector
-     *   A jQuery selector string.
-     * @param {string|object} response.value
-     *   The value of to be attached.
-     * @param {number} [status]
-     *   The XMLHttpRequest status.
-     */
-    data: function (ajax, response, status) {
+    data: function data(ajax, response, status) {
       $(response.selector).data(response.name, response.value);
     },
 
-    /**
-     * Command to apply a jQuery method.
-     *
-     * @param {Drupal.Ajax} [ajax]
-     *   {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
-     * @param {object} response
-     *   The response from the Ajax request.
-     * @param {Array} response.args
-     *   An array of arguments to the jQuery method, if any.
-     * @param {string} response.method
-     *   The jQuery method to invoke.
-     * @param {string} response.selector
-     *   A jQuery selector string.
-     * @param {number} [status]
-     *   The XMLHttpRequest status.
-     */
-    invoke: function (ajax, response, status) {
+    invoke: function invoke(ajax, response, status) {
       var $element = $(response.selector);
       $element[response.method].apply($element, response.args);
     },
 
-    /**
-     * Command to restripe a table.
-     *
-     * @param {Drupal.Ajax} [ajax]
-     *   {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
-     * @param {object} response
-     *   The response from the Ajax request.
-     * @param {string} response.selector
-     *   A jQuery selector string.
-     * @param {number} [status]
-     *   The XMLHttpRequest status.
-     */
-    restripe: function (ajax, response, status) {
-      // :even and :odd are reversed because jQuery counts from 0 and
-      // we count from 1, so we're out of sync.
-      // Match immediate children of the parent element to allow nesting.
-      $(response.selector).find('> tbody > tr:visible, > tr:visible')
-        .removeClass('odd even')
-        .filter(':even').addClass('odd').end()
-        .filter(':odd').addClass('even');
+    restripe: function restripe(ajax, response, status) {
+      $(response.selector).find('> tbody > tr:visible, > tr:visible').removeClass('odd even').filter(':even').addClass('odd').end().filter(':odd').addClass('even');
     },
 
-    /**
-     * Command to update a form's build ID.
-     *
-     * @param {Drupal.Ajax} [ajax]
-     *   {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
-     * @param {object} response
-     *   The response from the Ajax request.
-     * @param {string} response.old
-     *   The old form build ID.
-     * @param {string} response.new
-     *   The new form build ID.
-     * @param {number} [status]
-     *   The XMLHttpRequest status.
-     */
-    update_build_id: function (ajax, response, status) {
+    update_build_id: function update_build_id(ajax, response, status) {
       $('input[name="form_build_id"][value="' + response.old + '"]').val(response.new);
     },
 
-    /**
-     * Command to add css.
-     *
-     * Uses the proprietary addImport method if available as browsers which
-     * support that method ignore @import statements in dynamically added
-     * stylesheets.
-     *
-     * @param {Drupal.Ajax} [ajax]
-     *   {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
-     * @param {object} response
-     *   The response from the Ajax request.
-     * @param {string} response.data
-     *   A string that contains the styles to be added.
-     * @param {number} [status]
-     *   The XMLHttpRequest status.
-     */
-    add_css: function (ajax, response, status) {
-      // Add the styles in the normal way.
+    add_css: function add_css(ajax, response, status) {
       $('head').prepend(response.data);
-      // Add imports in the styles using the addImport method if available.
+
       var match;
       var importMatch = /^@import url\("(.*)"\);$/igm;
       if (document.styleSheets[0].addImport && importMatch.test(response.data)) {
@@ -1340,5 +598,4 @@
       }
     }
   };
-
-})(jQuery, window, Drupal, drupalSettings);
+})(jQuery, window, Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/misc/announce.es6.js b/core/misc/announce.es6.js
new file mode 100644
index 000000000000..acf850a64147
--- /dev/null
+++ b/core/misc/announce.es6.js
@@ -0,0 +1,120 @@
+/**
+ * @file
+ * Adds an HTML element and method to trigger audio UAs to read system messages.
+ *
+ * Use {@link Drupal.announce} to indicate to screen reader users that an
+ * element on the page has changed state. For instance, if clicking a link
+ * loads 10 more items into a list, one might announce the change like this.
+ *
+ * @example
+ * $('#search-list')
+ *   .on('itemInsert', function (event, data) {
+ *     // Insert the new items.
+ *     $(data.container.el).append(data.items.el);
+ *     // Announce the change to the page contents.
+ *     Drupal.announce(Drupal.t('@count items added to @container',
+ *       {'@count': data.items.length, '@container': data.container.title}
+ *     ));
+ *   });
+ */
+
+(function (Drupal, debounce) {
+
+  'use strict';
+
+  var liveElement;
+  var announcements = [];
+
+  /**
+   * Builds a div element with the aria-live attribute and add it to the DOM.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches the behavior for drupalAnnouce.
+   */
+  Drupal.behaviors.drupalAnnounce = {
+    attach: function (context) {
+      // Create only one aria-live element.
+      if (!liveElement) {
+        liveElement = document.createElement('div');
+        liveElement.id = 'drupal-live-announce';
+        liveElement.className = 'visually-hidden';
+        liveElement.setAttribute('aria-live', 'polite');
+        liveElement.setAttribute('aria-busy', 'false');
+        document.body.appendChild(liveElement);
+      }
+    }
+  };
+
+  /**
+   * Concatenates announcements to a single string; appends to the live region.
+   */
+  function announce() {
+    var text = [];
+    var priority = 'polite';
+    var announcement;
+
+    // Create an array of announcement strings to be joined and appended to the
+    // aria live region.
+    var il = announcements.length;
+    for (var i = 0; i < il; i++) {
+      announcement = announcements.pop();
+      text.unshift(announcement.text);
+      // If any of the announcements has a priority of assertive then the group
+      // of joined announcements will have this priority.
+      if (announcement.priority === 'assertive') {
+        priority = 'assertive';
+      }
+    }
+
+    if (text.length) {
+      // Clear the liveElement so that repeated strings will be read.
+      liveElement.innerHTML = '';
+      // Set the busy state to true until the node changes are complete.
+      liveElement.setAttribute('aria-busy', 'true');
+      // Set the priority to assertive, or default to polite.
+      liveElement.setAttribute('aria-live', priority);
+      // Print the text to the live region. Text should be run through
+      // Drupal.t() before being passed to Drupal.announce().
+      liveElement.innerHTML = text.join('\n');
+      // The live text area is updated. Allow the AT to announce the text.
+      liveElement.setAttribute('aria-busy', 'false');
+    }
+  }
+
+  /**
+   * Triggers audio UAs to read the supplied text.
+   *
+   * The aria-live region will only read the text that currently populates its
+   * text node. Replacing text quickly in rapid calls to announce results in
+   * only the text from the most recent call to {@link Drupal.announce} being
+   * read. By wrapping the call to announce in a debounce function, we allow for
+   * time for multiple calls to {@link Drupal.announce} to queue up their
+   * messages. These messages are then joined and append to the aria-live region
+   * as one text node.
+   *
+   * @param {string} text
+   *   A string to be read by the UA.
+   * @param {string} [priority='polite']
+   *   A string to indicate the priority of the message. Can be either
+   *   'polite' or 'assertive'.
+   *
+   * @return {function}
+   *   The return of the call to debounce.
+   *
+   * @see http://www.w3.org/WAI/PF/aria-practices/#liveprops
+   */
+  Drupal.announce = function (text, priority) {
+    // Save the text and priority into a closure variable. Multiple simultaneous
+    // announcements will be concatenated and read in sequence.
+    announcements.push({
+      text: text,
+      priority: priority
+    });
+    // Immediately invoke the function that debounce returns. 200 ms is right at
+    // the cusp where humans notice a pause, so we will wait
+    // at most this much time before the set of queued announcements is read.
+    return (debounce(announce, 200)());
+  };
+}(Drupal, Drupal.debounce));
diff --git a/core/misc/announce.js b/core/misc/announce.js
index acf850a64147..27ca35861c12 100644
--- a/core/misc/announce.js
+++ b/core/misc/announce.js
@@ -1,22 +1,10 @@
 /**
- * @file
- * Adds an HTML element and method to trigger audio UAs to read system messages.
- *
- * Use {@link Drupal.announce} to indicate to screen reader users that an
- * element on the page has changed state. For instance, if clicking a link
- * loads 10 more items into a list, one might announce the change like this.
- *
- * @example
- * $('#search-list')
- *   .on('itemInsert', function (event, data) {
- *     // Insert the new items.
- *     $(data.container.el).append(data.items.el);
- *     // Announce the change to the page contents.
- *     Drupal.announce(Drupal.t('@count items added to @container',
- *       {'@count': data.items.length, '@container': data.container.title}
- *     ));
- *   });
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./misc/announce.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function (Drupal, debounce) {
 
@@ -25,17 +13,8 @@
   var liveElement;
   var announcements = [];
 
-  /**
-   * Builds a div element with the aria-live attribute and add it to the DOM.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches the behavior for drupalAnnouce.
-   */
   Drupal.behaviors.drupalAnnounce = {
-    attach: function (context) {
-      // Create only one aria-live element.
+    attach: function attach(context) {
       if (!liveElement) {
         liveElement = document.createElement('div');
         liveElement.id = 'drupal-live-announce';
@@ -47,74 +26,40 @@
     }
   };
 
-  /**
-   * Concatenates announcements to a single string; appends to the live region.
-   */
   function announce() {
     var text = [];
     var priority = 'polite';
     var announcement;
 
-    // Create an array of announcement strings to be joined and appended to the
-    // aria live region.
     var il = announcements.length;
     for (var i = 0; i < il; i++) {
       announcement = announcements.pop();
       text.unshift(announcement.text);
-      // If any of the announcements has a priority of assertive then the group
-      // of joined announcements will have this priority.
+
       if (announcement.priority === 'assertive') {
         priority = 'assertive';
       }
     }
 
     if (text.length) {
-      // Clear the liveElement so that repeated strings will be read.
       liveElement.innerHTML = '';
-      // Set the busy state to true until the node changes are complete.
+
       liveElement.setAttribute('aria-busy', 'true');
-      // Set the priority to assertive, or default to polite.
+
       liveElement.setAttribute('aria-live', priority);
-      // Print the text to the live region. Text should be run through
-      // Drupal.t() before being passed to Drupal.announce().
+
       liveElement.innerHTML = text.join('\n');
-      // The live text area is updated. Allow the AT to announce the text.
+
       liveElement.setAttribute('aria-busy', 'false');
     }
   }
 
-  /**
-   * Triggers audio UAs to read the supplied text.
-   *
-   * The aria-live region will only read the text that currently populates its
-   * text node. Replacing text quickly in rapid calls to announce results in
-   * only the text from the most recent call to {@link Drupal.announce} being
-   * read. By wrapping the call to announce in a debounce function, we allow for
-   * time for multiple calls to {@link Drupal.announce} to queue up their
-   * messages. These messages are then joined and append to the aria-live region
-   * as one text node.
-   *
-   * @param {string} text
-   *   A string to be read by the UA.
-   * @param {string} [priority='polite']
-   *   A string to indicate the priority of the message. Can be either
-   *   'polite' or 'assertive'.
-   *
-   * @return {function}
-   *   The return of the call to debounce.
-   *
-   * @see http://www.w3.org/WAI/PF/aria-practices/#liveprops
-   */
   Drupal.announce = function (text, priority) {
-    // Save the text and priority into a closure variable. Multiple simultaneous
-    // announcements will be concatenated and read in sequence.
     announcements.push({
       text: text,
       priority: priority
     });
-    // Immediately invoke the function that debounce returns. 200 ms is right at
-    // the cusp where humans notice a pause, so we will wait
-    // at most this much time before the set of queued announcements is read.
-    return (debounce(announce, 200)());
+
+    return debounce(announce, 200)();
   };
-}(Drupal, Drupal.debounce));
+})(Drupal, Drupal.debounce);
\ No newline at end of file
diff --git a/core/misc/autocomplete.es6.js b/core/misc/autocomplete.es6.js
new file mode 100644
index 000000000000..5a1c156d0bbf
--- /dev/null
+++ b/core/misc/autocomplete.es6.js
@@ -0,0 +1,288 @@
+/**
+ * @file
+ * Autocomplete based on jQuery UI.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  var autocomplete;
+
+  /**
+   * Helper splitting terms from the autocomplete value.
+   *
+   * @function Drupal.autocomplete.splitValues
+   *
+   * @param {string} value
+   *   The value being entered by the user.
+   *
+   * @return {Array}
+   *   Array of values, split by comma.
+   */
+  function autocompleteSplitValues(value) {
+    // We will match the value against comma-separated terms.
+    var result = [];
+    var quote = false;
+    var current = '';
+    var valueLength = value.length;
+    var character;
+
+    for (var i = 0; i < valueLength; i++) {
+      character = value.charAt(i);
+      if (character === '"') {
+        current += character;
+        quote = !quote;
+      }
+      else if (character === ',' && !quote) {
+        result.push(current.trim());
+        current = '';
+      }
+      else {
+        current += character;
+      }
+    }
+    if (value.length > 0) {
+      result.push($.trim(current));
+    }
+
+    return result;
+  }
+
+  /**
+   * Returns the last value of an multi-value textfield.
+   *
+   * @function Drupal.autocomplete.extractLastTerm
+   *
+   * @param {string} terms
+   *   The value of the field.
+   *
+   * @return {string}
+   *   The last value of the input field.
+   */
+  function extractLastTerm(terms) {
+    return autocomplete.splitValues(terms).pop();
+  }
+
+  /**
+   * The search handler is called before a search is performed.
+   *
+   * @function Drupal.autocomplete.options.search
+   *
+   * @param {object} event
+   *   The event triggered.
+   *
+   * @return {bool}
+   *   Whether to perform a search or not.
+   */
+  function searchHandler(event) {
+    var options = autocomplete.options;
+
+    if (options.isComposing) {
+      return false;
+    }
+
+    var term = autocomplete.extractLastTerm(event.target.value);
+    // Abort search if the first character is in firstCharacterBlacklist.
+    if (term.length > 0 && options.firstCharacterBlacklist.indexOf(term[0]) !== -1) {
+      return false;
+    }
+    // Only search when the term is at least the minimum length.
+    return term.length >= options.minLength;
+  }
+
+  /**
+   * JQuery UI autocomplete source callback.
+   *
+   * @param {object} request
+   *   The request object.
+   * @param {function} response
+   *   The function to call with the response.
+   */
+  function sourceData(request, response) {
+    var elementId = this.element.attr('id');
+
+    if (!(elementId in autocomplete.cache)) {
+      autocomplete.cache[elementId] = {};
+    }
+
+    /**
+     * Filter through the suggestions removing all terms already tagged and
+     * display the available terms to the user.
+     *
+     * @param {object} suggestions
+     *   Suggestions returned by the server.
+     */
+    function showSuggestions(suggestions) {
+      var tagged = autocomplete.splitValues(request.term);
+      var il = tagged.length;
+      for (var i = 0; i < il; i++) {
+        var index = suggestions.indexOf(tagged[i]);
+        if (index >= 0) {
+          suggestions.splice(index, 1);
+        }
+      }
+      response(suggestions);
+    }
+
+    /**
+     * Transforms the data object into an array and update autocomplete results.
+     *
+     * @param {object} data
+     *   The data sent back from the server.
+     */
+    function sourceCallbackHandler(data) {
+      autocomplete.cache[elementId][term] = data;
+
+      // Send the new string array of terms to the jQuery UI list.
+      showSuggestions(data);
+    }
+
+    // Get the desired term and construct the autocomplete URL for it.
+    var term = autocomplete.extractLastTerm(request.term);
+
+    // Check if the term is already cached.
+    if (autocomplete.cache[elementId].hasOwnProperty(term)) {
+      showSuggestions(autocomplete.cache[elementId][term]);
+    }
+    else {
+      var options = $.extend({success: sourceCallbackHandler, data: {q: term}}, autocomplete.ajax);
+      $.ajax(this.element.attr('data-autocomplete-path'), options);
+    }
+  }
+
+  /**
+   * Handles an autocompletefocus event.
+   *
+   * @return {bool}
+   *   Always returns false.
+   */
+  function focusHandler() {
+    return false;
+  }
+
+  /**
+   * Handles an autocompleteselect event.
+   *
+   * @param {jQuery.Event} event
+   *   The event triggered.
+   * @param {object} ui
+   *   The jQuery UI settings object.
+   *
+   * @return {bool}
+   *   Returns false to indicate the event status.
+   */
+  function selectHandler(event, ui) {
+    var terms = autocomplete.splitValues(event.target.value);
+    // Remove the current input.
+    terms.pop();
+    // Add the selected item.
+    if (ui.item.value.search(',') > 0) {
+      terms.push('"' + ui.item.value + '"');
+    }
+    else {
+      terms.push(ui.item.value);
+    }
+    event.target.value = terms.join(', ');
+    // Return false to tell jQuery UI that we've filled in the value already.
+    return false;
+  }
+
+  /**
+   * Override jQuery UI _renderItem function to output HTML by default.
+   *
+   * @param {jQuery} ul
+   *   jQuery collection of the ul element.
+   * @param {object} item
+   *   The list item to append.
+   *
+   * @return {jQuery}
+   *   jQuery collection of the ul element.
+   */
+  function renderItem(ul, item) {
+    return $('<li>')
+      .append($('<a>').html(item.label))
+      .appendTo(ul);
+  }
+
+  /**
+   * Attaches the autocomplete behavior to all required fields.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches the autocomplete behaviors.
+   * @prop {Drupal~behaviorDetach} detach
+   *   Detaches the autocomplete behaviors.
+   */
+  Drupal.behaviors.autocomplete = {
+    attach: function (context) {
+      // Act on textfields with the "form-autocomplete" class.
+      var $autocomplete = $(context).find('input.form-autocomplete').once('autocomplete');
+      if ($autocomplete.length) {
+        // Allow options to be overriden per instance.
+        var blacklist = $autocomplete.attr('data-autocomplete-first-character-blacklist');
+        $.extend(autocomplete.options, {
+          firstCharacterBlacklist: (blacklist) ? blacklist : ''
+        });
+        // Use jQuery UI Autocomplete on the textfield.
+        $autocomplete.autocomplete(autocomplete.options)
+          .each(function () {
+            $(this).data('ui-autocomplete')._renderItem = autocomplete.options.renderItem;
+          });
+
+        // Use CompositionEvent to handle IME inputs. It requests remote server on "compositionend" event only.
+        $autocomplete.on('compositionstart.autocomplete', function () {
+          autocomplete.options.isComposing = true;
+        });
+        $autocomplete.on('compositionend.autocomplete', function () {
+          autocomplete.options.isComposing = false;
+        });
+      }
+    },
+    detach: function (context, settings, trigger) {
+      if (trigger === 'unload') {
+        $(context).find('input.form-autocomplete')
+          .removeOnce('autocomplete')
+          .autocomplete('destroy');
+      }
+    }
+  };
+
+  /**
+   * Autocomplete object implementation.
+   *
+   * @namespace Drupal.autocomplete
+   */
+  autocomplete = {
+    cache: {},
+    // Exposes options to allow overriding by contrib.
+    splitValues: autocompleteSplitValues,
+    extractLastTerm: extractLastTerm,
+    // jQuery UI autocomplete options.
+
+    /**
+     * JQuery UI option object.
+     *
+     * @name Drupal.autocomplete.options
+     */
+    options: {
+      source: sourceData,
+      focus: focusHandler,
+      search: searchHandler,
+      select: selectHandler,
+      renderItem: renderItem,
+      minLength: 1,
+      // Custom options, used by Drupal.autocomplete.
+      firstCharacterBlacklist: '',
+      // Custom options, indicate IME usage status.
+      isComposing: false
+    },
+    ajax: {
+      dataType: 'json'
+    }
+  };
+
+  Drupal.autocomplete = autocomplete;
+
+})(jQuery, Drupal);
diff --git a/core/misc/autocomplete.js b/core/misc/autocomplete.js
index 5a1c156d0bbf..04cc50a98a86 100644
--- a/core/misc/autocomplete.js
+++ b/core/misc/autocomplete.js
@@ -1,7 +1,10 @@
 /**
- * @file
- * Autocomplete based on jQuery UI.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./misc/autocomplete.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
@@ -9,19 +12,7 @@
 
   var autocomplete;
 
-  /**
-   * Helper splitting terms from the autocomplete value.
-   *
-   * @function Drupal.autocomplete.splitValues
-   *
-   * @param {string} value
-   *   The value being entered by the user.
-   *
-   * @return {Array}
-   *   Array of values, split by comma.
-   */
   function autocompleteSplitValues(value) {
-    // We will match the value against comma-separated terms.
     var result = [];
     var quote = false;
     var current = '';
@@ -33,12 +24,10 @@
       if (character === '"') {
         current += character;
         quote = !quote;
-      }
-      else if (character === ',' && !quote) {
+      } else if (character === ',' && !quote) {
         result.push(current.trim());
         current = '';
-      }
-      else {
+      } else {
         current += character;
       }
     }
@@ -49,32 +38,10 @@
     return result;
   }
 
-  /**
-   * Returns the last value of an multi-value textfield.
-   *
-   * @function Drupal.autocomplete.extractLastTerm
-   *
-   * @param {string} terms
-   *   The value of the field.
-   *
-   * @return {string}
-   *   The last value of the input field.
-   */
   function extractLastTerm(terms) {
     return autocomplete.splitValues(terms).pop();
   }
 
-  /**
-   * The search handler is called before a search is performed.
-   *
-   * @function Drupal.autocomplete.options.search
-   *
-   * @param {object} event
-   *   The event triggered.
-   *
-   * @return {bool}
-   *   Whether to perform a search or not.
-   */
   function searchHandler(event) {
     var options = autocomplete.options;
 
@@ -83,22 +50,14 @@
     }
 
     var term = autocomplete.extractLastTerm(event.target.value);
-    // Abort search if the first character is in firstCharacterBlacklist.
+
     if (term.length > 0 && options.firstCharacterBlacklist.indexOf(term[0]) !== -1) {
       return false;
     }
-    // Only search when the term is at least the minimum length.
+
     return term.length >= options.minLength;
   }
 
-  /**
-   * JQuery UI autocomplete source callback.
-   *
-   * @param {object} request
-   *   The request object.
-   * @param {function} response
-   *   The function to call with the response.
-   */
   function sourceData(request, response) {
     var elementId = this.element.attr('id');
 
@@ -106,13 +65,6 @@
       autocomplete.cache[elementId] = {};
     }
 
-    /**
-     * Filter through the suggestions removing all terms already tagged and
-     * display the available terms to the user.
-     *
-     * @param {object} suggestions
-     *   Suggestions returned by the server.
-     */
     function showSuggestions(suggestions) {
       var tagged = autocomplete.splitValues(request.term);
       var il = tagged.length;
@@ -125,113 +77,58 @@
       response(suggestions);
     }
 
-    /**
-     * Transforms the data object into an array and update autocomplete results.
-     *
-     * @param {object} data
-     *   The data sent back from the server.
-     */
     function sourceCallbackHandler(data) {
       autocomplete.cache[elementId][term] = data;
 
-      // Send the new string array of terms to the jQuery UI list.
       showSuggestions(data);
     }
 
-    // Get the desired term and construct the autocomplete URL for it.
     var term = autocomplete.extractLastTerm(request.term);
 
-    // Check if the term is already cached.
     if (autocomplete.cache[elementId].hasOwnProperty(term)) {
       showSuggestions(autocomplete.cache[elementId][term]);
-    }
-    else {
-      var options = $.extend({success: sourceCallbackHandler, data: {q: term}}, autocomplete.ajax);
+    } else {
+      var options = $.extend({ success: sourceCallbackHandler, data: { q: term } }, autocomplete.ajax);
       $.ajax(this.element.attr('data-autocomplete-path'), options);
     }
   }
 
-  /**
-   * Handles an autocompletefocus event.
-   *
-   * @return {bool}
-   *   Always returns false.
-   */
   function focusHandler() {
     return false;
   }
 
-  /**
-   * Handles an autocompleteselect event.
-   *
-   * @param {jQuery.Event} event
-   *   The event triggered.
-   * @param {object} ui
-   *   The jQuery UI settings object.
-   *
-   * @return {bool}
-   *   Returns false to indicate the event status.
-   */
   function selectHandler(event, ui) {
     var terms = autocomplete.splitValues(event.target.value);
-    // Remove the current input.
+
     terms.pop();
-    // Add the selected item.
+
     if (ui.item.value.search(',') > 0) {
       terms.push('"' + ui.item.value + '"');
-    }
-    else {
+    } else {
       terms.push(ui.item.value);
     }
     event.target.value = terms.join(', ');
-    // Return false to tell jQuery UI that we've filled in the value already.
+
     return false;
   }
 
-  /**
-   * Override jQuery UI _renderItem function to output HTML by default.
-   *
-   * @param {jQuery} ul
-   *   jQuery collection of the ul element.
-   * @param {object} item
-   *   The list item to append.
-   *
-   * @return {jQuery}
-   *   jQuery collection of the ul element.
-   */
   function renderItem(ul, item) {
-    return $('<li>')
-      .append($('<a>').html(item.label))
-      .appendTo(ul);
+    return $('<li>').append($('<a>').html(item.label)).appendTo(ul);
   }
 
-  /**
-   * Attaches the autocomplete behavior to all required fields.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches the autocomplete behaviors.
-   * @prop {Drupal~behaviorDetach} detach
-   *   Detaches the autocomplete behaviors.
-   */
   Drupal.behaviors.autocomplete = {
-    attach: function (context) {
-      // Act on textfields with the "form-autocomplete" class.
+    attach: function attach(context) {
       var $autocomplete = $(context).find('input.form-autocomplete').once('autocomplete');
       if ($autocomplete.length) {
-        // Allow options to be overriden per instance.
         var blacklist = $autocomplete.attr('data-autocomplete-first-character-blacklist');
         $.extend(autocomplete.options, {
-          firstCharacterBlacklist: (blacklist) ? blacklist : ''
+          firstCharacterBlacklist: blacklist ? blacklist : ''
+        });
+
+        $autocomplete.autocomplete(autocomplete.options).each(function () {
+          $(this).data('ui-autocomplete')._renderItem = autocomplete.options.renderItem;
         });
-        // Use jQuery UI Autocomplete on the textfield.
-        $autocomplete.autocomplete(autocomplete.options)
-          .each(function () {
-            $(this).data('ui-autocomplete')._renderItem = autocomplete.options.renderItem;
-          });
 
-        // Use CompositionEvent to handle IME inputs. It requests remote server on "compositionend" event only.
         $autocomplete.on('compositionstart.autocomplete', function () {
           autocomplete.options.isComposing = true;
         });
@@ -240,32 +137,19 @@
         });
       }
     },
-    detach: function (context, settings, trigger) {
+    detach: function detach(context, settings, trigger) {
       if (trigger === 'unload') {
-        $(context).find('input.form-autocomplete')
-          .removeOnce('autocomplete')
-          .autocomplete('destroy');
+        $(context).find('input.form-autocomplete').removeOnce('autocomplete').autocomplete('destroy');
       }
     }
   };
 
-  /**
-   * Autocomplete object implementation.
-   *
-   * @namespace Drupal.autocomplete
-   */
   autocomplete = {
     cache: {},
-    // Exposes options to allow overriding by contrib.
+
     splitValues: autocompleteSplitValues,
     extractLastTerm: extractLastTerm,
-    // jQuery UI autocomplete options.
 
-    /**
-     * JQuery UI option object.
-     *
-     * @name Drupal.autocomplete.options
-     */
     options: {
       source: sourceData,
       focus: focusHandler,
@@ -273,9 +157,9 @@
       select: selectHandler,
       renderItem: renderItem,
       minLength: 1,
-      // Custom options, used by Drupal.autocomplete.
+
       firstCharacterBlacklist: '',
-      // Custom options, indicate IME usage status.
+
       isComposing: false
     },
     ajax: {
@@ -284,5 +168,4 @@
   };
 
   Drupal.autocomplete = autocomplete;
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/misc/batch.es6.js b/core/misc/batch.es6.js
new file mode 100644
index 000000000000..411badba4272
--- /dev/null
+++ b/core/misc/batch.es6.js
@@ -0,0 +1,46 @@
+/**
+ * @file
+ * Drupal's batch API.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * Attaches the batch behavior to progress bars.
+   *
+   * @type {Drupal~behavior}
+   */
+  Drupal.behaviors.batch = {
+    attach: function (context, settings) {
+      var batch = settings.batch;
+      var $progress = $('[data-drupal-progress]').once('batch');
+      var progressBar;
+
+      // Success: redirect to the summary.
+      function updateCallback(progress, status, pb) {
+        if (progress === '100') {
+          pb.stopMonitoring();
+          window.location = batch.uri + '&op=finished';
+        }
+      }
+
+      function errorCallback(pb) {
+        $progress.prepend($('<p class="error"></p>').html(batch.errorMessage));
+        $('#wait').hide();
+      }
+
+      if ($progress.length) {
+        progressBar = new Drupal.ProgressBar('updateprogress', updateCallback, 'POST', errorCallback);
+        progressBar.setProgress(-1, batch.initMessage);
+        progressBar.startMonitoring(batch.uri + '&op=do', 10);
+        // Remove HTML from no-js progress bar.
+        $progress.empty();
+        // Append the JS progressbar element.
+        $progress.append(progressBar.element);
+      }
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/core/misc/batch.js b/core/misc/batch.js
index 411badba4272..6a2858bfa938 100644
--- a/core/misc/batch.js
+++ b/core/misc/batch.js
@@ -1,24 +1,21 @@
 /**
- * @file
- * Drupal's batch API.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./misc/batch.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   * Attaches the batch behavior to progress bars.
-   *
-   * @type {Drupal~behavior}
-   */
   Drupal.behaviors.batch = {
-    attach: function (context, settings) {
+    attach: function attach(context, settings) {
       var batch = settings.batch;
       var $progress = $('[data-drupal-progress]').once('batch');
       var progressBar;
 
-      // Success: redirect to the summary.
       function updateCallback(progress, status, pb) {
         if (progress === '100') {
           pb.stopMonitoring();
@@ -35,12 +32,11 @@
         progressBar = new Drupal.ProgressBar('updateprogress', updateCallback, 'POST', errorCallback);
         progressBar.setProgress(-1, batch.initMessage);
         progressBar.startMonitoring(batch.uri + '&op=do', 10);
-        // Remove HTML from no-js progress bar.
+
         $progress.empty();
-        // Append the JS progressbar element.
+
         $progress.append(progressBar.element);
       }
     }
   };
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/misc/collapse.es6.js b/core/misc/collapse.es6.js
new file mode 100644
index 000000000000..767325ec1ad7
--- /dev/null
+++ b/core/misc/collapse.es6.js
@@ -0,0 +1,146 @@
+/**
+ * @file
+ * Polyfill for HTML5 details elements.
+ */
+
+(function ($, Modernizr, Drupal) {
+
+  'use strict';
+
+  /**
+   * The collapsible details object represents a single details element.
+   *
+   * @constructor Drupal.CollapsibleDetails
+   *
+   * @param {HTMLElement} node
+   *   The details element.
+   */
+  function CollapsibleDetails(node) {
+    this.$node = $(node);
+    this.$node.data('details', this);
+    // Expand details if there are errors inside, or if it contains an
+    // element that is targeted by the URI fragment identifier.
+    var anchor = location.hash && location.hash !== '#' ? ', ' + location.hash : '';
+    if (this.$node.find('.error' + anchor).length) {
+      this.$node.attr('open', true);
+    }
+    // Initialize and setup the summary,
+    this.setupSummary();
+    // Initialize and setup the legend.
+    this.setupLegend();
+  }
+
+  $.extend(CollapsibleDetails, /** @lends Drupal.CollapsibleDetails */{
+
+    /**
+     * Holds references to instantiated CollapsibleDetails objects.
+     *
+     * @type {Array.<Drupal.CollapsibleDetails>}
+     */
+    instances: []
+  });
+
+  $.extend(CollapsibleDetails.prototype, /** @lends Drupal.CollapsibleDetails# */{
+
+    /**
+     * Initialize and setup summary events and markup.
+     *
+     * @fires event:summaryUpdated
+     *
+     * @listens event:summaryUpdated
+     */
+    setupSummary: function () {
+      this.$summary = $('<span class="summary"></span>');
+      this.$node
+        .on('summaryUpdated', $.proxy(this.onSummaryUpdated, this))
+        .trigger('summaryUpdated');
+    },
+
+    /**
+     * Initialize and setup legend markup.
+     */
+    setupLegend: function () {
+      // Turn the summary into a clickable link.
+      var $legend = this.$node.find('> summary');
+
+      $('<span class="details-summary-prefix visually-hidden"></span>')
+        .append(this.$node.attr('open') ? Drupal.t('Hide') : Drupal.t('Show'))
+        .prependTo($legend)
+        .after(document.createTextNode(' '));
+
+      // .wrapInner() does not retain bound events.
+      $('<a class="details-title"></a>')
+        .attr('href', '#' + this.$node.attr('id'))
+        .prepend($legend.contents())
+        .appendTo($legend);
+
+      $legend
+        .append(this.$summary)
+        .on('click', $.proxy(this.onLegendClick, this));
+    },
+
+    /**
+     * Handle legend clicks.
+     *
+     * @param {jQuery.Event} e
+     *   The event triggered.
+     */
+    onLegendClick: function (e) {
+      this.toggle();
+      e.preventDefault();
+    },
+
+    /**
+     * Update summary.
+     */
+    onSummaryUpdated: function () {
+      var text = $.trim(this.$node.drupalGetSummary());
+      this.$summary.html(text ? ' (' + text + ')' : '');
+    },
+
+    /**
+     * Toggle the visibility of a details element using smooth animations.
+     */
+    toggle: function () {
+      var isOpen = !!this.$node.attr('open');
+      var $summaryPrefix = this.$node.find('> summary span.details-summary-prefix');
+      if (isOpen) {
+        $summaryPrefix.html(Drupal.t('Show'));
+      }
+      else {
+        $summaryPrefix.html(Drupal.t('Hide'));
+      }
+      // Delay setting the attribute to emulate chrome behavior and make
+      // details-aria.js work as expected with this polyfill.
+      setTimeout(function () {
+        this.$node.attr('open', !isOpen);
+      }.bind(this), 0);
+    }
+  });
+
+  /**
+   * Polyfill HTML5 details element.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches behavior for the details element.
+   */
+  Drupal.behaviors.collapse = {
+    attach: function (context) {
+      if (Modernizr.details) {
+        return;
+      }
+      var $collapsibleDetails = $(context).find('details').once('collapse').addClass('collapse-processed');
+      if ($collapsibleDetails.length) {
+        for (var i = 0; i < $collapsibleDetails.length; i++) {
+          CollapsibleDetails.instances.push(new CollapsibleDetails($collapsibleDetails[i]));
+        }
+      }
+    }
+  };
+
+  // Expose constructor in the public space.
+  Drupal.CollapsibleDetails = CollapsibleDetails;
+
+})(jQuery, Modernizr, Drupal);
diff --git a/core/misc/collapse.js b/core/misc/collapse.js
index 767325ec1ad7..49ed00fee242 100644
--- a/core/misc/collapse.js
+++ b/core/misc/collapse.js
@@ -1,133 +1,76 @@
 /**
- * @file
- * Polyfill for HTML5 details elements.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./misc/collapse.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Modernizr, Drupal) {
 
   'use strict';
 
-  /**
-   * The collapsible details object represents a single details element.
-   *
-   * @constructor Drupal.CollapsibleDetails
-   *
-   * @param {HTMLElement} node
-   *   The details element.
-   */
   function CollapsibleDetails(node) {
     this.$node = $(node);
     this.$node.data('details', this);
-    // Expand details if there are errors inside, or if it contains an
-    // element that is targeted by the URI fragment identifier.
+
     var anchor = location.hash && location.hash !== '#' ? ', ' + location.hash : '';
     if (this.$node.find('.error' + anchor).length) {
       this.$node.attr('open', true);
     }
-    // Initialize and setup the summary,
+
     this.setupSummary();
-    // Initialize and setup the legend.
+
     this.setupLegend();
   }
 
-  $.extend(CollapsibleDetails, /** @lends Drupal.CollapsibleDetails */{
-
-    /**
-     * Holds references to instantiated CollapsibleDetails objects.
-     *
-     * @type {Array.<Drupal.CollapsibleDetails>}
-     */
+  $.extend(CollapsibleDetails, {
     instances: []
   });
 
-  $.extend(CollapsibleDetails.prototype, /** @lends Drupal.CollapsibleDetails# */{
-
-    /**
-     * Initialize and setup summary events and markup.
-     *
-     * @fires event:summaryUpdated
-     *
-     * @listens event:summaryUpdated
-     */
-    setupSummary: function () {
+  $.extend(CollapsibleDetails.prototype, {
+    setupSummary: function setupSummary() {
       this.$summary = $('<span class="summary"></span>');
-      this.$node
-        .on('summaryUpdated', $.proxy(this.onSummaryUpdated, this))
-        .trigger('summaryUpdated');
+      this.$node.on('summaryUpdated', $.proxy(this.onSummaryUpdated, this)).trigger('summaryUpdated');
     },
 
-    /**
-     * Initialize and setup legend markup.
-     */
-    setupLegend: function () {
-      // Turn the summary into a clickable link.
+    setupLegend: function setupLegend() {
       var $legend = this.$node.find('> summary');
 
-      $('<span class="details-summary-prefix visually-hidden"></span>')
-        .append(this.$node.attr('open') ? Drupal.t('Hide') : Drupal.t('Show'))
-        .prependTo($legend)
-        .after(document.createTextNode(' '));
+      $('<span class="details-summary-prefix visually-hidden"></span>').append(this.$node.attr('open') ? Drupal.t('Hide') : Drupal.t('Show')).prependTo($legend).after(document.createTextNode(' '));
 
-      // .wrapInner() does not retain bound events.
-      $('<a class="details-title"></a>')
-        .attr('href', '#' + this.$node.attr('id'))
-        .prepend($legend.contents())
-        .appendTo($legend);
+      $('<a class="details-title"></a>').attr('href', '#' + this.$node.attr('id')).prepend($legend.contents()).appendTo($legend);
 
-      $legend
-        .append(this.$summary)
-        .on('click', $.proxy(this.onLegendClick, this));
+      $legend.append(this.$summary).on('click', $.proxy(this.onLegendClick, this));
     },
 
-    /**
-     * Handle legend clicks.
-     *
-     * @param {jQuery.Event} e
-     *   The event triggered.
-     */
-    onLegendClick: function (e) {
+    onLegendClick: function onLegendClick(e) {
       this.toggle();
       e.preventDefault();
     },
 
-    /**
-     * Update summary.
-     */
-    onSummaryUpdated: function () {
+    onSummaryUpdated: function onSummaryUpdated() {
       var text = $.trim(this.$node.drupalGetSummary());
       this.$summary.html(text ? ' (' + text + ')' : '');
     },
 
-    /**
-     * Toggle the visibility of a details element using smooth animations.
-     */
-    toggle: function () {
+    toggle: function toggle() {
       var isOpen = !!this.$node.attr('open');
       var $summaryPrefix = this.$node.find('> summary span.details-summary-prefix');
       if (isOpen) {
         $summaryPrefix.html(Drupal.t('Show'));
-      }
-      else {
+      } else {
         $summaryPrefix.html(Drupal.t('Hide'));
       }
-      // Delay setting the attribute to emulate chrome behavior and make
-      // details-aria.js work as expected with this polyfill.
+
       setTimeout(function () {
         this.$node.attr('open', !isOpen);
       }.bind(this), 0);
     }
   });
 
-  /**
-   * Polyfill HTML5 details element.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches behavior for the details element.
-   */
   Drupal.behaviors.collapse = {
-    attach: function (context) {
+    attach: function attach(context) {
       if (Modernizr.details) {
         return;
       }
@@ -140,7 +83,5 @@
     }
   };
 
-  // Expose constructor in the public space.
   Drupal.CollapsibleDetails = CollapsibleDetails;
-
-})(jQuery, Modernizr, Drupal);
+})(jQuery, Modernizr, Drupal);
\ No newline at end of file
diff --git a/core/misc/date.es6.js b/core/misc/date.es6.js
new file mode 100644
index 000000000000..8b6b71cb1db0
--- /dev/null
+++ b/core/misc/date.es6.js
@@ -0,0 +1,56 @@
+/**
+ * @file
+ * Polyfill for HTML5 date input.
+ */
+
+(function ($, Modernizr, Drupal) {
+
+  'use strict';
+
+  /**
+   * Attach datepicker fallback on date elements.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches the behavior. Accepts in `settings.date` an object listing
+   *   elements to process, keyed by the HTML ID of the form element containing
+   *   the human-readable value. Each element is an datepicker settings object.
+   * @prop {Drupal~behaviorDetach} detach
+   *   Detach the behavior destroying datepickers on effected elements.
+   */
+  Drupal.behaviors.date = {
+    attach: function (context, settings) {
+      var $context = $(context);
+      // Skip if date are supported by the browser.
+      if (Modernizr.inputtypes.date === true) {
+        return;
+      }
+      $context.find('input[data-drupal-date-format]').once('datePicker').each(function () {
+        var $input = $(this);
+        var datepickerSettings = {};
+        var dateFormat = $input.data('drupalDateFormat');
+        // The date format is saved in PHP style, we need to convert to jQuery
+        // datepicker.
+        datepickerSettings.dateFormat = dateFormat
+          .replace('Y', 'yy')
+          .replace('m', 'mm')
+          .replace('d', 'dd');
+        // Add min and max date if set on the input.
+        if ($input.attr('min')) {
+          datepickerSettings.minDate = $input.attr('min');
+        }
+        if ($input.attr('max')) {
+          datepickerSettings.maxDate = $input.attr('max');
+        }
+        $input.datepicker(datepickerSettings);
+      });
+    },
+    detach: function (context, settings, trigger) {
+      if (trigger === 'unload') {
+        $(context).find('input[data-drupal-date-format]').findOnce('datePicker').datepicker('destroy');
+      }
+    }
+  };
+
+})(jQuery, Modernizr, Drupal);
diff --git a/core/misc/date.js b/core/misc/date.js
index 8b6b71cb1db0..89dcd7abea3f 100644
--- a/core/misc/date.js
+++ b/core/misc/date.js
@@ -1,28 +1,19 @@
 /**
- * @file
- * Polyfill for HTML5 date input.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./misc/date.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Modernizr, Drupal) {
 
   'use strict';
 
-  /**
-   * Attach datepicker fallback on date elements.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches the behavior. Accepts in `settings.date` an object listing
-   *   elements to process, keyed by the HTML ID of the form element containing
-   *   the human-readable value. Each element is an datepicker settings object.
-   * @prop {Drupal~behaviorDetach} detach
-   *   Detach the behavior destroying datepickers on effected elements.
-   */
   Drupal.behaviors.date = {
-    attach: function (context, settings) {
+    attach: function attach(context, settings) {
       var $context = $(context);
-      // Skip if date are supported by the browser.
+
       if (Modernizr.inputtypes.date === true) {
         return;
       }
@@ -30,13 +21,9 @@
         var $input = $(this);
         var datepickerSettings = {};
         var dateFormat = $input.data('drupalDateFormat');
-        // The date format is saved in PHP style, we need to convert to jQuery
-        // datepicker.
-        datepickerSettings.dateFormat = dateFormat
-          .replace('Y', 'yy')
-          .replace('m', 'mm')
-          .replace('d', 'dd');
-        // Add min and max date if set on the input.
+
+        datepickerSettings.dateFormat = dateFormat.replace('Y', 'yy').replace('m', 'mm').replace('d', 'dd');
+
         if ($input.attr('min')) {
           datepickerSettings.minDate = $input.attr('min');
         }
@@ -46,11 +33,10 @@
         $input.datepicker(datepickerSettings);
       });
     },
-    detach: function (context, settings, trigger) {
+    detach: function detach(context, settings, trigger) {
       if (trigger === 'unload') {
         $(context).find('input[data-drupal-date-format]').findOnce('datePicker').datepicker('destroy');
       }
     }
   };
-
-})(jQuery, Modernizr, Drupal);
+})(jQuery, Modernizr, Drupal);
\ No newline at end of file
diff --git a/core/misc/debounce.es6.js b/core/misc/debounce.es6.js
new file mode 100644
index 000000000000..995e3d713593
--- /dev/null
+++ b/core/misc/debounce.es6.js
@@ -0,0 +1,52 @@
+/**
+ * @file
+ * Adapted from underscore.js with the addition Drupal namespace.
+ */
+
+/**
+ * Limits the invocations of a function in a given time frame.
+ *
+ * The debounce function wrapper should be used sparingly. One clear use case
+ * is limiting the invocation of a callback attached to the window resize event.
+ *
+ * Before using the debounce function wrapper, consider first whether the
+ * callback could be attached to an event that fires less frequently or if the
+ * function can be written in such a way that it is only invoked under specific
+ * conditions.
+ *
+ * @param {function} func
+ *   The function to be invoked.
+ * @param {number} wait
+ *   The time period within which the callback function should only be
+ *   invoked once. For example if the wait period is 250ms, then the callback
+ *   will only be called at most 4 times per second.
+ * @param {bool} immediate
+ *   Whether we wait at the beginning or end to execute the function.
+ *
+ * @return {function}
+ *   The debounced function.
+ */
+Drupal.debounce = function (func, wait, immediate) {
+
+  'use strict';
+
+  var timeout;
+  var result;
+  return function () {
+    var context = this;
+    var args = arguments;
+    var later = function () {
+      timeout = null;
+      if (!immediate) {
+        result = func.apply(context, args);
+      }
+    };
+    var callNow = immediate && !timeout;
+    clearTimeout(timeout);
+    timeout = setTimeout(later, wait);
+    if (callNow) {
+      result = func.apply(context, args);
+    }
+    return result;
+  };
+};
diff --git a/core/misc/debounce.js b/core/misc/debounce.js
index 995e3d713593..2ad1f872556c 100644
--- a/core/misc/debounce.js
+++ b/core/misc/debounce.js
@@ -1,31 +1,11 @@
 /**
- * @file
- * Adapted from underscore.js with the addition Drupal namespace.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./misc/debounce.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
-/**
- * Limits the invocations of a function in a given time frame.
- *
- * The debounce function wrapper should be used sparingly. One clear use case
- * is limiting the invocation of a callback attached to the window resize event.
- *
- * Before using the debounce function wrapper, consider first whether the
- * callback could be attached to an event that fires less frequently or if the
- * function can be written in such a way that it is only invoked under specific
- * conditions.
- *
- * @param {function} func
- *   The function to be invoked.
- * @param {number} wait
- *   The time period within which the callback function should only be
- *   invoked once. For example if the wait period is 250ms, then the callback
- *   will only be called at most 4 times per second.
- * @param {bool} immediate
- *   Whether we wait at the beginning or end to execute the function.
- *
- * @return {function}
- *   The debounced function.
- */
 Drupal.debounce = function (func, wait, immediate) {
 
   'use strict';
@@ -35,7 +15,7 @@ Drupal.debounce = function (func, wait, immediate) {
   return function () {
     var context = this;
     var args = arguments;
-    var later = function () {
+    var later = function later() {
       timeout = null;
       if (!immediate) {
         result = func.apply(context, args);
@@ -49,4 +29,4 @@ Drupal.debounce = function (func, wait, immediate) {
     }
     return result;
   };
-};
+};
\ No newline at end of file
diff --git a/core/misc/details-aria.es6.js b/core/misc/details-aria.es6.js
new file mode 100644
index 000000000000..d341422351c8
--- /dev/null
+++ b/core/misc/details-aria.es6.js
@@ -0,0 +1,29 @@
+/**
+ * @file
+ * Add aria attribute handling for details and summary elements.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * Handles `aria-expanded` and `aria-pressed` attributes on details elements.
+   *
+   * @type {Drupal~behavior}
+   */
+  Drupal.behaviors.detailsAria = {
+    attach: function () {
+      $('body').once('detailsAria').on('click.detailsAria', 'summary', function (event) {
+        var $summary = $(event.currentTarget);
+        var open = $(event.currentTarget.parentNode).attr('open') === 'open' ? 'false' : 'true';
+
+        $summary.attr({
+          'aria-expanded': open,
+          'aria-pressed': open
+        });
+      });
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/core/misc/details-aria.js b/core/misc/details-aria.js
index d341422351c8..833146cf4759 100644
--- a/core/misc/details-aria.js
+++ b/core/misc/details-aria.js
@@ -1,19 +1,17 @@
 /**
- * @file
- * Add aria attribute handling for details and summary elements.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./misc/details-aria.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   * Handles `aria-expanded` and `aria-pressed` attributes on details elements.
-   *
-   * @type {Drupal~behavior}
-   */
   Drupal.behaviors.detailsAria = {
-    attach: function () {
+    attach: function attach() {
       $('body').once('detailsAria').on('click.detailsAria', 'summary', function (event) {
         var $summary = $(event.currentTarget);
         var open = $(event.currentTarget.parentNode).attr('open') === 'open' ? 'false' : 'true';
@@ -25,5 +23,4 @@
       });
     }
   };
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/misc/dialog/dialog.ajax.es6.js b/core/misc/dialog/dialog.ajax.es6.js
new file mode 100644
index 000000000000..3f1b0c2efd6f
--- /dev/null
+++ b/core/misc/dialog/dialog.ajax.es6.js
@@ -0,0 +1,246 @@
+/**
+ * @file
+ * Extends the Drupal AJAX functionality to integrate the dialog API.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * Initialize dialogs for Ajax purposes.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches the behaviors for dialog ajax functionality.
+   */
+  Drupal.behaviors.dialog = {
+    attach: function (context, settings) {
+      var $context = $(context);
+
+      // Provide a known 'drupal-modal' DOM element for Drupal-based modal
+      // dialogs. Non-modal dialogs are responsible for creating their own
+      // elements, since there can be multiple non-modal dialogs at a time.
+      if (!$('#drupal-modal').length) {
+        // Add 'ui-front' jQuery UI class so jQuery UI widgets like autocomplete
+        // sit on top of dialogs. For more information see
+        // http://api.jqueryui.com/theming/stacking-elements/.
+        $('<div id="drupal-modal" class="ui-front"/>').hide().appendTo('body');
+      }
+
+      // Special behaviors specific when attaching content within a dialog.
+      // These behaviors usually fire after a validation error inside a dialog.
+      var $dialog = $context.closest('.ui-dialog-content');
+      if ($dialog.length) {
+        // Remove and replace the dialog buttons with those from the new form.
+        if ($dialog.dialog('option', 'drupalAutoButtons')) {
+          // Trigger an event to detect/sync changes to buttons.
+          $dialog.trigger('dialogButtonsChange');
+        }
+
+        // Force focus on the modal when the behavior is run.
+        $dialog.dialog('widget').trigger('focus');
+      }
+
+      var originalClose = settings.dialog.close;
+      // Overwrite the close method to remove the dialog on closing.
+      settings.dialog.close = function (event) {
+        originalClose.apply(settings.dialog, arguments);
+        $(event.target).remove();
+      };
+    },
+
+    /**
+     * Scan a dialog for any primary buttons and move them to the button area.
+     *
+     * @param {jQuery} $dialog
+     *   An jQuery object containing the element that is the dialog target.
+     *
+     * @return {Array}
+     *   An array of buttons that need to be added to the button area.
+     */
+    prepareDialogButtons: function ($dialog) {
+      var buttons = [];
+      var $buttons = $dialog.find('.form-actions input[type=submit], .form-actions a.button');
+      $buttons.each(function () {
+        // Hidden form buttons need special attention. For browser consistency,
+        // the button needs to be "visible" in order to have the enter key fire
+        // the form submit event. So instead of a simple "hide" or
+        // "display: none", we set its dimensions to zero.
+        // See http://mattsnider.com/how-forms-submit-when-pressing-enter/
+        var $originalButton = $(this).css({
+          display: 'block',
+          width: 0,
+          height: 0,
+          padding: 0,
+          border: 0,
+          overflow: 'hidden'
+        });
+        buttons.push({
+          text: $originalButton.html() || $originalButton.attr('value'),
+          class: $originalButton.attr('class'),
+          click: function (e) {
+            // If the original button is an anchor tag, triggering the "click"
+            // event will not simulate a click. Use the click method instead.
+            if ($originalButton.is('a')) {
+              $originalButton[0].click();
+            }
+            else {
+              $originalButton.trigger('mousedown').trigger('mouseup').trigger('click');
+              e.preventDefault();
+            }
+          }
+        });
+      });
+      return buttons;
+    }
+  };
+
+  /**
+   * Command to open a dialog.
+   *
+   * @param {Drupal.Ajax} ajax
+   *   The Drupal Ajax object.
+   * @param {object} response
+   *   Object holding the server response.
+   * @param {number} [status]
+   *   The HTTP status code.
+   *
+   * @return {bool|undefined}
+   *   Returns false if there was no selector property in the response object.
+   */
+  Drupal.AjaxCommands.prototype.openDialog = function (ajax, response, status) {
+    if (!response.selector) {
+      return false;
+    }
+    var $dialog = $(response.selector);
+    if (!$dialog.length) {
+      // Create the element if needed.
+      $dialog = $('<div id="' + response.selector.replace(/^#/, '') + '" class="ui-front"/>').appendTo('body');
+    }
+    // Set up the wrapper, if there isn't one.
+    if (!ajax.wrapper) {
+      ajax.wrapper = $dialog.attr('id');
+    }
+
+    // Use the ajax.js insert command to populate the dialog contents.
+    response.command = 'insert';
+    response.method = 'html';
+    ajax.commands.insert(ajax, response, status);
+
+    // Move the buttons to the jQuery UI dialog buttons area.
+    if (!response.dialogOptions.buttons) {
+      response.dialogOptions.drupalAutoButtons = true;
+      response.dialogOptions.buttons = Drupal.behaviors.dialog.prepareDialogButtons($dialog);
+    }
+
+    // Bind dialogButtonsChange.
+    $dialog.on('dialogButtonsChange', function () {
+      var buttons = Drupal.behaviors.dialog.prepareDialogButtons($dialog);
+      $dialog.dialog('option', 'buttons', buttons);
+    });
+
+    // Open the dialog itself.
+    response.dialogOptions = response.dialogOptions || {};
+    var dialog = Drupal.dialog($dialog.get(0), response.dialogOptions);
+    if (response.dialogOptions.modal) {
+      dialog.showModal();
+    }
+    else {
+      dialog.show();
+    }
+
+    // Add the standard Drupal class for buttons for style consistency.
+    $dialog.parent().find('.ui-dialog-buttonset').addClass('form-actions');
+  };
+
+  /**
+   * Command to close a dialog.
+   *
+   * If no selector is given, it defaults to trying to close the modal.
+   *
+   * @param {Drupal.Ajax} [ajax]
+   *   The ajax object.
+   * @param {object} response
+   *   Object holding the server response.
+   * @param {string} response.selector
+   *   The selector of the dialog.
+   * @param {bool} response.persist
+   *   Whether to persist the dialog element or not.
+   * @param {number} [status]
+   *   The HTTP status code.
+   */
+  Drupal.AjaxCommands.prototype.closeDialog = function (ajax, response, status) {
+    var $dialog = $(response.selector);
+    if ($dialog.length) {
+      Drupal.dialog($dialog.get(0)).close();
+      if (!response.persist) {
+        $dialog.remove();
+      }
+    }
+
+    // Unbind dialogButtonsChange.
+    $dialog.off('dialogButtonsChange');
+  };
+
+  /**
+   * Command to set a dialog property.
+   *
+   * JQuery UI specific way of setting dialog options.
+   *
+   * @param {Drupal.Ajax} [ajax]
+   *   The Drupal Ajax object.
+   * @param {object} response
+   *   Object holding the server response.
+   * @param {string} response.selector
+   *   Selector for the dialog element.
+   * @param {string} response.optionsName
+   *   Name of a key to set.
+   * @param {string} response.optionValue
+   *   Value to set.
+   * @param {number} [status]
+   *   The HTTP status code.
+   */
+  Drupal.AjaxCommands.prototype.setDialogOption = function (ajax, response, status) {
+    var $dialog = $(response.selector);
+    if ($dialog.length) {
+      $dialog.dialog('option', response.optionName, response.optionValue);
+    }
+  };
+
+  /**
+   * Binds a listener on dialog creation to handle the cancel link.
+   *
+   * @param {jQuery.Event} e
+   *   The event triggered.
+   * @param {Drupal.dialog~dialogDefinition} dialog
+   *   The dialog instance.
+   * @param {jQuery} $element
+   *   The jQuery collection of the dialog element.
+   * @param {object} [settings]
+   *   Dialog settings.
+   */
+  $(window).on('dialog:aftercreate', function (e, dialog, $element, settings) {
+    $element.on('click.dialog', '.dialog-cancel', function (e) {
+      dialog.close('cancel');
+      e.preventDefault();
+      e.stopPropagation();
+    });
+  });
+
+  /**
+   * Removes all 'dialog' listeners.
+   *
+   * @param {jQuery.Event} e
+   *   The event triggered.
+   * @param {Drupal.dialog~dialogDefinition} dialog
+   *   The dialog instance.
+   * @param {jQuery} $element
+   *   jQuery collection of the dialog element.
+   */
+  $(window).on('dialog:beforeclose', function (e, dialog, $element) {
+    $element.off('.dialog');
+  });
+
+})(jQuery, Drupal);
diff --git a/core/misc/dialog/dialog.ajax.js b/core/misc/dialog/dialog.ajax.js
index 3f1b0c2efd6f..0ab7e74d3c3c 100644
--- a/core/misc/dialog/dialog.ajax.js
+++ b/core/misc/dialog/dialog.ajax.js
@@ -1,74 +1,44 @@
 /**
- * @file
- * Extends the Drupal AJAX functionality to integrate the dialog API.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./misc/dialog/dialog.ajax.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   * Initialize dialogs for Ajax purposes.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches the behaviors for dialog ajax functionality.
-   */
   Drupal.behaviors.dialog = {
-    attach: function (context, settings) {
+    attach: function attach(context, settings) {
       var $context = $(context);
 
-      // Provide a known 'drupal-modal' DOM element for Drupal-based modal
-      // dialogs. Non-modal dialogs are responsible for creating their own
-      // elements, since there can be multiple non-modal dialogs at a time.
       if (!$('#drupal-modal').length) {
-        // Add 'ui-front' jQuery UI class so jQuery UI widgets like autocomplete
-        // sit on top of dialogs. For more information see
-        // http://api.jqueryui.com/theming/stacking-elements/.
         $('<div id="drupal-modal" class="ui-front"/>').hide().appendTo('body');
       }
 
-      // Special behaviors specific when attaching content within a dialog.
-      // These behaviors usually fire after a validation error inside a dialog.
       var $dialog = $context.closest('.ui-dialog-content');
       if ($dialog.length) {
-        // Remove and replace the dialog buttons with those from the new form.
         if ($dialog.dialog('option', 'drupalAutoButtons')) {
-          // Trigger an event to detect/sync changes to buttons.
           $dialog.trigger('dialogButtonsChange');
         }
 
-        // Force focus on the modal when the behavior is run.
         $dialog.dialog('widget').trigger('focus');
       }
 
       var originalClose = settings.dialog.close;
-      // Overwrite the close method to remove the dialog on closing.
+
       settings.dialog.close = function (event) {
         originalClose.apply(settings.dialog, arguments);
         $(event.target).remove();
       };
     },
 
-    /**
-     * Scan a dialog for any primary buttons and move them to the button area.
-     *
-     * @param {jQuery} $dialog
-     *   An jQuery object containing the element that is the dialog target.
-     *
-     * @return {Array}
-     *   An array of buttons that need to be added to the button area.
-     */
-    prepareDialogButtons: function ($dialog) {
+    prepareDialogButtons: function prepareDialogButtons($dialog) {
       var buttons = [];
       var $buttons = $dialog.find('.form-actions input[type=submit], .form-actions a.button');
       $buttons.each(function () {
-        // Hidden form buttons need special attention. For browser consistency,
-        // the button needs to be "visible" in order to have the enter key fire
-        // the form submit event. So instead of a simple "hide" or
-        // "display: none", we set its dimensions to zero.
-        // See http://mattsnider.com/how-forms-submit-when-pressing-enter/
         var $originalButton = $(this).css({
           display: 'block',
           width: 0,
@@ -80,13 +50,10 @@
         buttons.push({
           text: $originalButton.html() || $originalButton.attr('value'),
           class: $originalButton.attr('class'),
-          click: function (e) {
-            // If the original button is an anchor tag, triggering the "click"
-            // event will not simulate a click. Use the click method instead.
+          click: function click(e) {
             if ($originalButton.is('a')) {
               $originalButton[0].click();
-            }
-            else {
+            } else {
               $originalButton.trigger('mousedown').trigger('mouseup').trigger('click');
               e.preventDefault();
             }
@@ -97,80 +64,44 @@
     }
   };
 
-  /**
-   * Command to open a dialog.
-   *
-   * @param {Drupal.Ajax} ajax
-   *   The Drupal Ajax object.
-   * @param {object} response
-   *   Object holding the server response.
-   * @param {number} [status]
-   *   The HTTP status code.
-   *
-   * @return {bool|undefined}
-   *   Returns false if there was no selector property in the response object.
-   */
   Drupal.AjaxCommands.prototype.openDialog = function (ajax, response, status) {
     if (!response.selector) {
       return false;
     }
     var $dialog = $(response.selector);
     if (!$dialog.length) {
-      // Create the element if needed.
       $dialog = $('<div id="' + response.selector.replace(/^#/, '') + '" class="ui-front"/>').appendTo('body');
     }
-    // Set up the wrapper, if there isn't one.
+
     if (!ajax.wrapper) {
       ajax.wrapper = $dialog.attr('id');
     }
 
-    // Use the ajax.js insert command to populate the dialog contents.
     response.command = 'insert';
     response.method = 'html';
     ajax.commands.insert(ajax, response, status);
 
-    // Move the buttons to the jQuery UI dialog buttons area.
     if (!response.dialogOptions.buttons) {
       response.dialogOptions.drupalAutoButtons = true;
       response.dialogOptions.buttons = Drupal.behaviors.dialog.prepareDialogButtons($dialog);
     }
 
-    // Bind dialogButtonsChange.
     $dialog.on('dialogButtonsChange', function () {
       var buttons = Drupal.behaviors.dialog.prepareDialogButtons($dialog);
       $dialog.dialog('option', 'buttons', buttons);
     });
 
-    // Open the dialog itself.
     response.dialogOptions = response.dialogOptions || {};
     var dialog = Drupal.dialog($dialog.get(0), response.dialogOptions);
     if (response.dialogOptions.modal) {
       dialog.showModal();
-    }
-    else {
+    } else {
       dialog.show();
     }
 
-    // Add the standard Drupal class for buttons for style consistency.
     $dialog.parent().find('.ui-dialog-buttonset').addClass('form-actions');
   };
 
-  /**
-   * Command to close a dialog.
-   *
-   * If no selector is given, it defaults to trying to close the modal.
-   *
-   * @param {Drupal.Ajax} [ajax]
-   *   The ajax object.
-   * @param {object} response
-   *   Object holding the server response.
-   * @param {string} response.selector
-   *   The selector of the dialog.
-   * @param {bool} response.persist
-   *   Whether to persist the dialog element or not.
-   * @param {number} [status]
-   *   The HTTP status code.
-   */
   Drupal.AjaxCommands.prototype.closeDialog = function (ajax, response, status) {
     var $dialog = $(response.selector);
     if ($dialog.length) {
@@ -180,28 +111,9 @@
       }
     }
 
-    // Unbind dialogButtonsChange.
     $dialog.off('dialogButtonsChange');
   };
 
-  /**
-   * Command to set a dialog property.
-   *
-   * JQuery UI specific way of setting dialog options.
-   *
-   * @param {Drupal.Ajax} [ajax]
-   *   The Drupal Ajax object.
-   * @param {object} response
-   *   Object holding the server response.
-   * @param {string} response.selector
-   *   Selector for the dialog element.
-   * @param {string} response.optionsName
-   *   Name of a key to set.
-   * @param {string} response.optionValue
-   *   Value to set.
-   * @param {number} [status]
-   *   The HTTP status code.
-   */
   Drupal.AjaxCommands.prototype.setDialogOption = function (ajax, response, status) {
     var $dialog = $(response.selector);
     if ($dialog.length) {
@@ -209,18 +121,6 @@
     }
   };
 
-  /**
-   * Binds a listener on dialog creation to handle the cancel link.
-   *
-   * @param {jQuery.Event} e
-   *   The event triggered.
-   * @param {Drupal.dialog~dialogDefinition} dialog
-   *   The dialog instance.
-   * @param {jQuery} $element
-   *   The jQuery collection of the dialog element.
-   * @param {object} [settings]
-   *   Dialog settings.
-   */
   $(window).on('dialog:aftercreate', function (e, dialog, $element, settings) {
     $element.on('click.dialog', '.dialog-cancel', function (e) {
       dialog.close('cancel');
@@ -229,18 +129,7 @@
     });
   });
 
-  /**
-   * Removes all 'dialog' listeners.
-   *
-   * @param {jQuery.Event} e
-   *   The event triggered.
-   * @param {Drupal.dialog~dialogDefinition} dialog
-   *   The dialog instance.
-   * @param {jQuery} $element
-   *   jQuery collection of the dialog element.
-   */
   $(window).on('dialog:beforeclose', function (e, dialog, $element) {
     $element.off('.dialog');
   });
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/misc/dialog/dialog.es6.js b/core/misc/dialog/dialog.es6.js
new file mode 100644
index 000000000000..ea1e52c8dc9a
--- /dev/null
+++ b/core/misc/dialog/dialog.es6.js
@@ -0,0 +1,100 @@
+/**
+ * @file
+ * Dialog API inspired by HTML5 dialog element.
+ *
+ * @see http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#the-dialog-element
+ */
+
+(function ($, Drupal, drupalSettings) {
+
+  'use strict';
+
+  /**
+   * Default dialog options.
+   *
+   * @type {object}
+   *
+   * @prop {bool} [autoOpen=true]
+   * @prop {string} [dialogClass='']
+   * @prop {string} [buttonClass='button']
+   * @prop {string} [buttonPrimaryClass='button--primary']
+   * @prop {function} close
+   */
+  drupalSettings.dialog = {
+    autoOpen: true,
+    dialogClass: '',
+    // Drupal-specific extensions: see dialog.jquery-ui.js.
+    buttonClass: 'button',
+    buttonPrimaryClass: 'button--primary',
+    // When using this API directly (when generating dialogs on the client
+    // side), you may want to override this method and do
+    // `jQuery(event.target).remove()` as well, to remove the dialog on
+    // closing.
+    close: function (event) {
+      Drupal.dialog(event.target).close();
+      Drupal.detachBehaviors(event.target, null, 'unload');
+    }
+  };
+
+  /**
+   * @typedef {object} Drupal.dialog~dialogDefinition
+   *
+   * @prop {boolean} open
+   *   Is the dialog open or not.
+   * @prop {*} returnValue
+   *   Return value of the dialog.
+   * @prop {function} show
+   *   Method to display the dialog on the page.
+   * @prop {function} showModal
+   *   Method to display the dialog as a modal on the page.
+   * @prop {function} close
+   *   Method to hide the dialog from the page.
+   */
+
+  /**
+   * Polyfill HTML5 dialog element with jQueryUI.
+   *
+   * @param {HTMLElement} element
+   *   The element that holds the dialog.
+   * @param {object} options
+   *   jQuery UI options to be passed to the dialog.
+   *
+   * @return {Drupal.dialog~dialogDefinition}
+   *   The dialog instance.
+   */
+  Drupal.dialog = function (element, options) {
+    var undef;
+    var $element = $(element);
+    var dialog = {
+      open: false,
+      returnValue: undef,
+      show: function () {
+        openDialog({modal: false});
+      },
+      showModal: function () {
+        openDialog({modal: true});
+      },
+      close: closeDialog
+    };
+
+    function openDialog(settings) {
+      settings = $.extend({}, drupalSettings.dialog, options, settings);
+      // Trigger a global event to allow scripts to bind events to the dialog.
+      $(window).trigger('dialog:beforecreate', [dialog, $element, settings]);
+      $element.dialog(settings);
+      dialog.open = true;
+      $(window).trigger('dialog:aftercreate', [dialog, $element, settings]);
+    }
+
+    function closeDialog(value) {
+      $(window).trigger('dialog:beforeclose', [dialog, $element]);
+      $element.dialog('close');
+      dialog.returnValue = value;
+      dialog.open = false;
+      $(window).trigger('dialog:afterclose', [dialog, $element]);
+    }
+
+    return dialog;
+  };
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/misc/dialog/dialog.jquery-ui.es6.js b/core/misc/dialog/dialog.jquery-ui.es6.js
new file mode 100644
index 000000000000..2526e30953b9
--- /dev/null
+++ b/core/misc/dialog/dialog.jquery-ui.es6.js
@@ -0,0 +1,36 @@
+/**
+ * @file
+ * Adds default classes to buttons for styling purposes.
+ */
+
+(function ($) {
+
+  'use strict';
+
+  $.widget('ui.dialog', $.ui.dialog, {
+    options: {
+      buttonClass: 'button',
+      buttonPrimaryClass: 'button--primary'
+    },
+    _createButtons: function () {
+      var opts = this.options;
+      var primaryIndex;
+      var $buttons;
+      var index;
+      var il = opts.buttons.length;
+      for (index = 0; index < il; index++) {
+        if (opts.buttons[index].primary && opts.buttons[index].primary === true) {
+          primaryIndex = index;
+          delete opts.buttons[index].primary;
+          break;
+        }
+      }
+      this._super();
+      $buttons = this.uiButtonSet.children().addClass(opts.buttonClass);
+      if (typeof primaryIndex !== 'undefined') {
+        $buttons.eq(index).addClass(opts.buttonPrimaryClass);
+      }
+    }
+  });
+
+})(jQuery);
diff --git a/core/misc/dialog/dialog.jquery-ui.js b/core/misc/dialog/dialog.jquery-ui.js
index 2526e30953b9..cfa777e0ae8c 100644
--- a/core/misc/dialog/dialog.jquery-ui.js
+++ b/core/misc/dialog/dialog.jquery-ui.js
@@ -1,7 +1,10 @@
 /**
- * @file
- * Adds default classes to buttons for styling purposes.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./misc/dialog/dialog.jquery-ui.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($) {
 
@@ -12,7 +15,7 @@
       buttonClass: 'button',
       buttonPrimaryClass: 'button--primary'
     },
-    _createButtons: function () {
+    _createButtons: function _createButtons() {
       var opts = this.options;
       var primaryIndex;
       var $buttons;
@@ -32,5 +35,4 @@
       }
     }
   });
-
-})(jQuery);
+})(jQuery);
\ No newline at end of file
diff --git a/core/misc/dialog/dialog.js b/core/misc/dialog/dialog.js
index ea1e52c8dc9a..3cd2b0b64b69 100644
--- a/core/misc/dialog/dialog.js
+++ b/core/misc/dialog/dialog.js
@@ -1,85 +1,46 @@
 /**
- * @file
- * Dialog API inspired by HTML5 dialog element.
- *
- * @see http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#the-dialog-element
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./misc/dialog/dialog.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings) {
 
   'use strict';
 
-  /**
-   * Default dialog options.
-   *
-   * @type {object}
-   *
-   * @prop {bool} [autoOpen=true]
-   * @prop {string} [dialogClass='']
-   * @prop {string} [buttonClass='button']
-   * @prop {string} [buttonPrimaryClass='button--primary']
-   * @prop {function} close
-   */
   drupalSettings.dialog = {
     autoOpen: true,
     dialogClass: '',
-    // Drupal-specific extensions: see dialog.jquery-ui.js.
+
     buttonClass: 'button',
     buttonPrimaryClass: 'button--primary',
-    // When using this API directly (when generating dialogs on the client
-    // side), you may want to override this method and do
-    // `jQuery(event.target).remove()` as well, to remove the dialog on
-    // closing.
-    close: function (event) {
+
+    close: function close(event) {
       Drupal.dialog(event.target).close();
       Drupal.detachBehaviors(event.target, null, 'unload');
     }
   };
 
-  /**
-   * @typedef {object} Drupal.dialog~dialogDefinition
-   *
-   * @prop {boolean} open
-   *   Is the dialog open or not.
-   * @prop {*} returnValue
-   *   Return value of the dialog.
-   * @prop {function} show
-   *   Method to display the dialog on the page.
-   * @prop {function} showModal
-   *   Method to display the dialog as a modal on the page.
-   * @prop {function} close
-   *   Method to hide the dialog from the page.
-   */
-
-  /**
-   * Polyfill HTML5 dialog element with jQueryUI.
-   *
-   * @param {HTMLElement} element
-   *   The element that holds the dialog.
-   * @param {object} options
-   *   jQuery UI options to be passed to the dialog.
-   *
-   * @return {Drupal.dialog~dialogDefinition}
-   *   The dialog instance.
-   */
   Drupal.dialog = function (element, options) {
     var undef;
     var $element = $(element);
     var dialog = {
       open: false,
       returnValue: undef,
-      show: function () {
-        openDialog({modal: false});
+      show: function show() {
+        openDialog({ modal: false });
       },
-      showModal: function () {
-        openDialog({modal: true});
+      showModal: function showModal() {
+        openDialog({ modal: true });
       },
       close: closeDialog
     };
 
     function openDialog(settings) {
       settings = $.extend({}, drupalSettings.dialog, options, settings);
-      // Trigger a global event to allow scripts to bind events to the dialog.
+
       $(window).trigger('dialog:beforecreate', [dialog, $element, settings]);
       $element.dialog(settings);
       dialog.open = true;
@@ -96,5 +57,4 @@
 
     return dialog;
   };
-
-})(jQuery, Drupal, drupalSettings);
+})(jQuery, Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/misc/dialog/dialog.position.es6.js b/core/misc/dialog/dialog.position.es6.js
new file mode 100644
index 000000000000..e3c058f8440a
--- /dev/null
+++ b/core/misc/dialog/dialog.position.es6.js
@@ -0,0 +1,112 @@
+/**
+ * @file
+ * Positioning extensions for dialogs.
+ */
+
+/**
+ * Triggers when content inside a dialog changes.
+ *
+ * @event dialogContentResize
+ */
+
+(function ($, Drupal, drupalSettings, debounce, displace) {
+
+  'use strict';
+
+  // autoResize option will turn off resizable and draggable.
+  drupalSettings.dialog = $.extend({autoResize: true, maxHeight: '95%'}, drupalSettings.dialog);
+
+  /**
+   * Resets the current options for positioning.
+   *
+   * This is used as a window resize and scroll callback to reposition the
+   * jQuery UI dialog. Although not a built-in jQuery UI option, this can
+   * be disabled by setting autoResize: false in the options array when creating
+   * a new {@link Drupal.dialog}.
+   *
+   * @function Drupal.dialog~resetSize
+   *
+   * @param {jQuery.Event} event
+   *   The event triggered.
+   *
+   * @fires event:dialogContentResize
+   */
+  function resetSize(event) {
+    var positionOptions = ['width', 'height', 'minWidth', 'minHeight', 'maxHeight', 'maxWidth', 'position'];
+    var adjustedOptions = {};
+    var windowHeight = $(window).height();
+    var option;
+    var optionValue;
+    var adjustedValue;
+    for (var n = 0; n < positionOptions.length; n++) {
+      option = positionOptions[n];
+      optionValue = event.data.settings[option];
+      if (optionValue) {
+        // jQuery UI does not support percentages on heights, convert to pixels.
+        if (typeof optionValue === 'string' && /%$/.test(optionValue) && /height/i.test(option)) {
+          // Take offsets in account.
+          windowHeight -= displace.offsets.top + displace.offsets.bottom;
+          adjustedValue = parseInt(0.01 * parseInt(optionValue, 10) * windowHeight, 10);
+          // Don't force the dialog to be bigger vertically than needed.
+          if (option === 'height' && event.data.$element.parent().outerHeight() < adjustedValue) {
+            adjustedValue = 'auto';
+          }
+          adjustedOptions[option] = adjustedValue;
+        }
+      }
+    }
+    // Offset the dialog center to be at the center of Drupal.displace.offsets.
+    if (!event.data.settings.modal) {
+      adjustedOptions = resetPosition(adjustedOptions);
+    }
+    event.data.$element
+      .dialog('option', adjustedOptions)
+      .trigger('dialogContentResize');
+  }
+
+  /**
+   * Position the dialog's center at the center of displace.offsets boundaries.
+   *
+   * @function Drupal.dialog~resetPosition
+   *
+   * @param {object} options
+   *   Options object.
+   *
+   * @return {object}
+   *   Altered options object.
+   */
+  function resetPosition(options) {
+    var offsets = displace.offsets;
+    var left = offsets.left - offsets.right;
+    var top = offsets.top - offsets.bottom;
+
+    var leftString = (left > 0 ? '+' : '-') + Math.abs(Math.round(left / 2)) + 'px';
+    var topString = (top > 0 ? '+' : '-') + Math.abs(Math.round(top / 2)) + 'px';
+    options.position = {
+      my: 'center' + (left !== 0 ? leftString : '') + ' center' + (top !== 0 ? topString : ''),
+      of: window
+    };
+    return options;
+  }
+
+  $(window).on({
+    'dialog:aftercreate': function (event, dialog, $element, settings) {
+      var autoResize = debounce(resetSize, 20);
+      var eventData = {settings: settings, $element: $element};
+      if (settings.autoResize === true || settings.autoResize === 'true') {
+        $element
+          .dialog('option', {resizable: false, draggable: false})
+          .dialog('widget').css('position', 'fixed');
+        $(window)
+          .on('resize.dialogResize scroll.dialogResize', eventData, autoResize)
+          .trigger('resize.dialogResize');
+        $(document).on('drupalViewportOffsetChange.dialogResize', eventData, autoResize);
+      }
+    },
+    'dialog:beforeclose': function (event, dialog, $element) {
+      $(window).off('.dialogResize');
+      $(document).off('.dialogResize');
+    }
+  });
+
+})(jQuery, Drupal, drupalSettings, Drupal.debounce, Drupal.displace);
diff --git a/core/misc/dialog/dialog.position.js b/core/misc/dialog/dialog.position.js
index e3c058f8440a..fac104896e24 100644
--- a/core/misc/dialog/dialog.position.js
+++ b/core/misc/dialog/dialog.position.js
@@ -1,36 +1,17 @@
 /**
- * @file
- * Positioning extensions for dialogs.
- */
-
-/**
- * Triggers when content inside a dialog changes.
- *
- * @event dialogContentResize
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./misc/dialog/dialog.position.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings, debounce, displace) {
 
   'use strict';
 
-  // autoResize option will turn off resizable and draggable.
-  drupalSettings.dialog = $.extend({autoResize: true, maxHeight: '95%'}, drupalSettings.dialog);
+  drupalSettings.dialog = $.extend({ autoResize: true, maxHeight: '95%' }, drupalSettings.dialog);
 
-  /**
-   * Resets the current options for positioning.
-   *
-   * This is used as a window resize and scroll callback to reposition the
-   * jQuery UI dialog. Although not a built-in jQuery UI option, this can
-   * be disabled by setting autoResize: false in the options array when creating
-   * a new {@link Drupal.dialog}.
-   *
-   * @function Drupal.dialog~resetSize
-   *
-   * @param {jQuery.Event} event
-   *   The event triggered.
-   *
-   * @fires event:dialogContentResize
-   */
   function resetSize(event) {
     var positionOptions = ['width', 'height', 'minWidth', 'minHeight', 'maxHeight', 'maxWidth', 'position'];
     var adjustedOptions = {};
@@ -42,12 +23,10 @@
       option = positionOptions[n];
       optionValue = event.data.settings[option];
       if (optionValue) {
-        // jQuery UI does not support percentages on heights, convert to pixels.
         if (typeof optionValue === 'string' && /%$/.test(optionValue) && /height/i.test(option)) {
-          // Take offsets in account.
           windowHeight -= displace.offsets.top + displace.offsets.bottom;
           adjustedValue = parseInt(0.01 * parseInt(optionValue, 10) * windowHeight, 10);
-          // Don't force the dialog to be bigger vertically than needed.
+
           if (option === 'height' && event.data.$element.parent().outerHeight() < adjustedValue) {
             adjustedValue = 'auto';
           }
@@ -55,26 +34,13 @@
         }
       }
     }
-    // Offset the dialog center to be at the center of Drupal.displace.offsets.
+
     if (!event.data.settings.modal) {
       adjustedOptions = resetPosition(adjustedOptions);
     }
-    event.data.$element
-      .dialog('option', adjustedOptions)
-      .trigger('dialogContentResize');
+    event.data.$element.dialog('option', adjustedOptions).trigger('dialogContentResize');
   }
 
-  /**
-   * Position the dialog's center at the center of displace.offsets boundaries.
-   *
-   * @function Drupal.dialog~resetPosition
-   *
-   * @param {object} options
-   *   Options object.
-   *
-   * @return {object}
-   *   Altered options object.
-   */
   function resetPosition(options) {
     var offsets = displace.offsets;
     var left = offsets.left - offsets.right;
@@ -90,23 +56,18 @@
   }
 
   $(window).on({
-    'dialog:aftercreate': function (event, dialog, $element, settings) {
+    'dialog:aftercreate': function dialogAftercreate(event, dialog, $element, settings) {
       var autoResize = debounce(resetSize, 20);
-      var eventData = {settings: settings, $element: $element};
+      var eventData = { settings: settings, $element: $element };
       if (settings.autoResize === true || settings.autoResize === 'true') {
-        $element
-          .dialog('option', {resizable: false, draggable: false})
-          .dialog('widget').css('position', 'fixed');
-        $(window)
-          .on('resize.dialogResize scroll.dialogResize', eventData, autoResize)
-          .trigger('resize.dialogResize');
+        $element.dialog('option', { resizable: false, draggable: false }).dialog('widget').css('position', 'fixed');
+        $(window).on('resize.dialogResize scroll.dialogResize', eventData, autoResize).trigger('resize.dialogResize');
         $(document).on('drupalViewportOffsetChange.dialogResize', eventData, autoResize);
       }
     },
-    'dialog:beforeclose': function (event, dialog, $element) {
+    'dialog:beforeclose': function dialogBeforeclose(event, dialog, $element) {
       $(window).off('.dialogResize');
       $(document).off('.dialogResize');
     }
   });
-
-})(jQuery, Drupal, drupalSettings, Drupal.debounce, Drupal.displace);
+})(jQuery, Drupal, drupalSettings, Drupal.debounce, Drupal.displace);
\ No newline at end of file
diff --git a/core/misc/displace.es6.js b/core/misc/displace.es6.js
new file mode 100644
index 000000000000..3e89c563ecfd
--- /dev/null
+++ b/core/misc/displace.es6.js
@@ -0,0 +1,222 @@
+/**
+ * @file
+ * Manages elements that can offset the size of the viewport.
+ *
+ * Measures and reports viewport offset dimensions from elements like the
+ * toolbar that can potentially displace the positioning of other elements.
+ */
+
+/**
+ * @typedef {object} Drupal~displaceOffset
+ *
+ * @prop {number} top
+ * @prop {number} left
+ * @prop {number} right
+ * @prop {number} bottom
+ */
+
+/**
+ * Triggers when layout of the page changes.
+ *
+ * This is used to position fixed element on the page during page resize and
+ * Toolbar toggling.
+ *
+ * @event drupalViewportOffsetChange
+ */
+
+(function ($, Drupal, debounce) {
+
+  'use strict';
+
+  /**
+   * @name Drupal.displace.offsets
+   *
+   * @type {Drupal~displaceOffset}
+   */
+  var offsets = {
+    top: 0,
+    right: 0,
+    bottom: 0,
+    left: 0
+  };
+
+  /**
+   * Registers a resize handler on the window.
+   *
+   * @type {Drupal~behavior}
+   */
+  Drupal.behaviors.drupalDisplace = {
+    attach: function () {
+      // Mark this behavior as processed on the first pass.
+      if (this.displaceProcessed) {
+        return;
+      }
+      this.displaceProcessed = true;
+
+      $(window).on('resize.drupalDisplace', debounce(displace, 200));
+    }
+  };
+
+  /**
+   * Informs listeners of the current offset dimensions.
+   *
+   * @function Drupal.displace
+   *
+   * @prop {Drupal~displaceOffset} offsets
+   *
+   * @param {bool} [broadcast]
+   *   When true or undefined, causes the recalculated offsets values to be
+   *   broadcast to listeners.
+   *
+   * @return {Drupal~displaceOffset}
+   *   An object whose keys are the for sides an element -- top, right, bottom
+   *   and left. The value of each key is the viewport displacement distance for
+   *   that edge.
+   *
+   * @fires event:drupalViewportOffsetChange
+   */
+  function displace(broadcast) {
+    offsets = Drupal.displace.offsets = calculateOffsets();
+    if (typeof broadcast === 'undefined' || broadcast) {
+      $(document).trigger('drupalViewportOffsetChange', offsets);
+    }
+    return offsets;
+  }
+
+  /**
+   * Determines the viewport offsets.
+   *
+   * @return {Drupal~displaceOffset}
+   *   An object whose keys are the for sides an element -- top, right, bottom
+   *   and left. The value of each key is the viewport displacement distance for
+   *   that edge.
+   */
+  function calculateOffsets() {
+    return {
+      top: calculateOffset('top'),
+      right: calculateOffset('right'),
+      bottom: calculateOffset('bottom'),
+      left: calculateOffset('left')
+    };
+  }
+
+  /**
+   * Gets a specific edge's offset.
+   *
+   * Any element with the attribute data-offset-{edge} e.g. data-offset-top will
+   * be considered in the viewport offset calculations. If the attribute has a
+   * numeric value, that value will be used. If no value is provided, one will
+   * be calculated using the element's dimensions and placement.
+   *
+   * @function Drupal.displace.calculateOffset
+   *
+   * @param {string} edge
+   *   The name of the edge to calculate. Can be 'top', 'right',
+   *   'bottom' or 'left'.
+   *
+   * @return {number}
+   *   The viewport displacement distance for the requested edge.
+   */
+  function calculateOffset(edge) {
+    var edgeOffset = 0;
+    var displacingElements = document.querySelectorAll('[data-offset-' + edge + ']');
+    var n = displacingElements.length;
+    for (var i = 0; i < n; i++) {
+      var el = displacingElements[i];
+      // If the element is not visible, do consider its dimensions.
+      if (el.style.display === 'none') {
+        continue;
+      }
+      // If the offset data attribute contains a displacing value, use it.
+      var displacement = parseInt(el.getAttribute('data-offset-' + edge), 10);
+      // If the element's offset data attribute exits
+      // but is not a valid number then get the displacement
+      // dimensions directly from the element.
+      if (isNaN(displacement)) {
+        displacement = getRawOffset(el, edge);
+      }
+      // If the displacement value is larger than the current value for this
+      // edge, use the displacement value.
+      edgeOffset = Math.max(edgeOffset, displacement);
+    }
+
+    return edgeOffset;
+  }
+
+  /**
+   * Calculates displacement for element based on its dimensions and placement.
+   *
+   * @param {HTMLElement} el
+   *   The jQuery element whose dimensions and placement will be measured.
+   *
+   * @param {string} edge
+   *   The name of the edge of the viewport that the element is associated
+   *   with.
+   *
+   * @return {number}
+   *   The viewport displacement distance for the requested edge.
+   */
+  function getRawOffset(el, edge) {
+    var $el = $(el);
+    var documentElement = document.documentElement;
+    var displacement = 0;
+    var horizontal = (edge === 'left' || edge === 'right');
+    // Get the offset of the element itself.
+    var placement = $el.offset()[horizontal ? 'left' : 'top'];
+    // Subtract scroll distance from placement to get the distance
+    // to the edge of the viewport.
+    placement -= window['scroll' + (horizontal ? 'X' : 'Y')] || document.documentElement['scroll' + (horizontal ? 'Left' : 'Top')] || 0;
+    // Find the displacement value according to the edge.
+    switch (edge) {
+      // Left and top elements displace as a sum of their own offset value
+      // plus their size.
+      case 'top':
+        // Total displacement is the sum of the elements placement and size.
+        displacement = placement + $el.outerHeight();
+        break;
+
+      case 'left':
+        // Total displacement is the sum of the elements placement and size.
+        displacement = placement + $el.outerWidth();
+        break;
+
+      // Right and bottom elements displace according to their left and
+      // top offset. Their size isn't important.
+      case 'bottom':
+        displacement = documentElement.clientHeight - placement;
+        break;
+
+      case 'right':
+        displacement = documentElement.clientWidth - placement;
+        break;
+
+      default:
+        displacement = 0;
+    }
+    return displacement;
+  }
+
+  /**
+   * Assign the displace function to a property of the Drupal global object.
+   *
+   * @ignore
+   */
+  Drupal.displace = displace;
+  $.extend(Drupal.displace, {
+
+    /**
+     * Expose offsets to other scripts to avoid having to recalculate offsets.
+     *
+     * @ignore
+     */
+    offsets: offsets,
+
+    /**
+     * Expose method to compute a single edge offsets.
+     *
+     * @ignore
+     */
+    calculateOffset: calculateOffset
+  });
+
+})(jQuery, Drupal, Drupal.debounce);
diff --git a/core/misc/displace.js b/core/misc/displace.js
index 3e89c563ecfd..da3f510dcbb4 100644
--- a/core/misc/displace.js
+++ b/core/misc/displace.js
@@ -1,38 +1,15 @@
 /**
- * @file
- * Manages elements that can offset the size of the viewport.
- *
- * Measures and reports viewport offset dimensions from elements like the
- * toolbar that can potentially displace the positioning of other elements.
- */
-
-/**
- * @typedef {object} Drupal~displaceOffset
- *
- * @prop {number} top
- * @prop {number} left
- * @prop {number} right
- * @prop {number} bottom
- */
-
-/**
- * Triggers when layout of the page changes.
- *
- * This is used to position fixed element on the page during page resize and
- * Toolbar toggling.
- *
- * @event drupalViewportOffsetChange
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./misc/displace.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, debounce) {
 
   'use strict';
 
-  /**
-   * @name Drupal.displace.offsets
-   *
-   * @type {Drupal~displaceOffset}
-   */
   var offsets = {
     top: 0,
     right: 0,
@@ -40,14 +17,8 @@
     left: 0
   };
 
-  /**
-   * Registers a resize handler on the window.
-   *
-   * @type {Drupal~behavior}
-   */
   Drupal.behaviors.drupalDisplace = {
-    attach: function () {
-      // Mark this behavior as processed on the first pass.
+    attach: function attach() {
       if (this.displaceProcessed) {
         return;
       }
@@ -57,24 +28,6 @@
     }
   };
 
-  /**
-   * Informs listeners of the current offset dimensions.
-   *
-   * @function Drupal.displace
-   *
-   * @prop {Drupal~displaceOffset} offsets
-   *
-   * @param {bool} [broadcast]
-   *   When true or undefined, causes the recalculated offsets values to be
-   *   broadcast to listeners.
-   *
-   * @return {Drupal~displaceOffset}
-   *   An object whose keys are the for sides an element -- top, right, bottom
-   *   and left. The value of each key is the viewport displacement distance for
-   *   that edge.
-   *
-   * @fires event:drupalViewportOffsetChange
-   */
   function displace(broadcast) {
     offsets = Drupal.displace.offsets = calculateOffsets();
     if (typeof broadcast === 'undefined' || broadcast) {
@@ -83,14 +36,6 @@
     return offsets;
   }
 
-  /**
-   * Determines the viewport offsets.
-   *
-   * @return {Drupal~displaceOffset}
-   *   An object whose keys are the for sides an element -- top, right, bottom
-   *   and left. The value of each key is the viewport displacement distance for
-   *   that edge.
-   */
   function calculateOffsets() {
     return {
       top: calculateOffset('top'),
@@ -100,88 +45,48 @@
     };
   }
 
-  /**
-   * Gets a specific edge's offset.
-   *
-   * Any element with the attribute data-offset-{edge} e.g. data-offset-top will
-   * be considered in the viewport offset calculations. If the attribute has a
-   * numeric value, that value will be used. If no value is provided, one will
-   * be calculated using the element's dimensions and placement.
-   *
-   * @function Drupal.displace.calculateOffset
-   *
-   * @param {string} edge
-   *   The name of the edge to calculate. Can be 'top', 'right',
-   *   'bottom' or 'left'.
-   *
-   * @return {number}
-   *   The viewport displacement distance for the requested edge.
-   */
   function calculateOffset(edge) {
     var edgeOffset = 0;
     var displacingElements = document.querySelectorAll('[data-offset-' + edge + ']');
     var n = displacingElements.length;
     for (var i = 0; i < n; i++) {
       var el = displacingElements[i];
-      // If the element is not visible, do consider its dimensions.
+
       if (el.style.display === 'none') {
         continue;
       }
-      // If the offset data attribute contains a displacing value, use it.
+
       var displacement = parseInt(el.getAttribute('data-offset-' + edge), 10);
-      // If the element's offset data attribute exits
-      // but is not a valid number then get the displacement
-      // dimensions directly from the element.
+
       if (isNaN(displacement)) {
         displacement = getRawOffset(el, edge);
       }
-      // If the displacement value is larger than the current value for this
-      // edge, use the displacement value.
+
       edgeOffset = Math.max(edgeOffset, displacement);
     }
 
     return edgeOffset;
   }
 
-  /**
-   * Calculates displacement for element based on its dimensions and placement.
-   *
-   * @param {HTMLElement} el
-   *   The jQuery element whose dimensions and placement will be measured.
-   *
-   * @param {string} edge
-   *   The name of the edge of the viewport that the element is associated
-   *   with.
-   *
-   * @return {number}
-   *   The viewport displacement distance for the requested edge.
-   */
   function getRawOffset(el, edge) {
     var $el = $(el);
     var documentElement = document.documentElement;
     var displacement = 0;
-    var horizontal = (edge === 'left' || edge === 'right');
-    // Get the offset of the element itself.
+    var horizontal = edge === 'left' || edge === 'right';
+
     var placement = $el.offset()[horizontal ? 'left' : 'top'];
-    // Subtract scroll distance from placement to get the distance
-    // to the edge of the viewport.
+
     placement -= window['scroll' + (horizontal ? 'X' : 'Y')] || document.documentElement['scroll' + (horizontal ? 'Left' : 'Top')] || 0;
-    // Find the displacement value according to the edge.
+
     switch (edge) {
-      // Left and top elements displace as a sum of their own offset value
-      // plus their size.
       case 'top':
-        // Total displacement is the sum of the elements placement and size.
         displacement = placement + $el.outerHeight();
         break;
 
       case 'left':
-        // Total displacement is the sum of the elements placement and size.
         displacement = placement + $el.outerWidth();
         break;
 
-      // Right and bottom elements displace according to their left and
-      // top offset. Their size isn't important.
       case 'bottom':
         displacement = documentElement.clientHeight - placement;
         break;
@@ -196,27 +101,10 @@
     return displacement;
   }
 
-  /**
-   * Assign the displace function to a property of the Drupal global object.
-   *
-   * @ignore
-   */
   Drupal.displace = displace;
   $.extend(Drupal.displace, {
-
-    /**
-     * Expose offsets to other scripts to avoid having to recalculate offsets.
-     *
-     * @ignore
-     */
     offsets: offsets,
 
-    /**
-     * Expose method to compute a single edge offsets.
-     *
-     * @ignore
-     */
     calculateOffset: calculateOffset
   });
-
-})(jQuery, Drupal, Drupal.debounce);
+})(jQuery, Drupal, Drupal.debounce);
\ No newline at end of file
diff --git a/core/misc/dropbutton/dropbutton.es6.js b/core/misc/dropbutton/dropbutton.es6.js
new file mode 100644
index 000000000000..04f9491ad739
--- /dev/null
+++ b/core/misc/dropbutton/dropbutton.es6.js
@@ -0,0 +1,233 @@
+/**
+ * @file
+ * Dropbutton feature.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * Process elements with the .dropbutton class on page load.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches dropButton behaviors.
+   */
+  Drupal.behaviors.dropButton = {
+    attach: function (context, settings) {
+      var $dropbuttons = $(context).find('.dropbutton-wrapper').once('dropbutton');
+      if ($dropbuttons.length) {
+        // Adds the delegated handler that will toggle dropdowns on click.
+        var $body = $('body').once('dropbutton-click');
+        if ($body.length) {
+          $body.on('click', '.dropbutton-toggle', dropbuttonClickHandler);
+        }
+        // Initialize all buttons.
+        var il = $dropbuttons.length;
+        for (var i = 0; i < il; i++) {
+          DropButton.dropbuttons.push(new DropButton($dropbuttons[i], settings.dropbutton));
+        }
+      }
+    }
+  };
+
+  /**
+   * Delegated callback for opening and closing dropbutton secondary actions.
+   *
+   * @function Drupal.DropButton~dropbuttonClickHandler
+   *
+   * @param {jQuery.Event} e
+   *   The event triggered.
+   */
+  function dropbuttonClickHandler(e) {
+    e.preventDefault();
+    $(e.target).closest('.dropbutton-wrapper').toggleClass('open');
+  }
+
+  /**
+   * A DropButton presents an HTML list as a button with a primary action.
+   *
+   * All secondary actions beyond the first in the list are presented in a
+   * dropdown list accessible through a toggle arrow associated with the button.
+   *
+   * @constructor Drupal.DropButton
+   *
+   * @param {HTMLElement} dropbutton
+   *   A DOM element.
+   * @param {object} settings
+   *   A list of options including:
+   * @param {string} settings.title
+   *   The text inside the toggle link element. This text is hidden
+   *   from visual UAs.
+   */
+  function DropButton(dropbutton, settings) {
+    // Merge defaults with settings.
+    var options = $.extend({title: Drupal.t('List additional actions')}, settings);
+    var $dropbutton = $(dropbutton);
+
+    /**
+     * @type {jQuery}
+     */
+    this.$dropbutton = $dropbutton;
+
+    /**
+     * @type {jQuery}
+     */
+    this.$list = $dropbutton.find('.dropbutton');
+
+    /**
+     * Find actions and mark them.
+     *
+     * @type {jQuery}
+     */
+    this.$actions = this.$list.find('li').addClass('dropbutton-action');
+
+    // Add the special dropdown only if there are hidden actions.
+    if (this.$actions.length > 1) {
+      // Identify the first element of the collection.
+      var $primary = this.$actions.slice(0, 1);
+      // Identify the secondary actions.
+      var $secondary = this.$actions.slice(1);
+      $secondary.addClass('secondary-action');
+      // Add toggle link.
+      $primary.after(Drupal.theme('dropbuttonToggle', options));
+      // Bind mouse events.
+      this.$dropbutton
+        .addClass('dropbutton-multiple')
+        .on({
+
+          /**
+           * Adds a timeout to close the dropdown on mouseleave.
+           *
+           * @ignore
+           */
+          'mouseleave.dropbutton': $.proxy(this.hoverOut, this),
+
+          /**
+           * Clears timeout when mouseout of the dropdown.
+           *
+           * @ignore
+           */
+          'mouseenter.dropbutton': $.proxy(this.hoverIn, this),
+
+          /**
+           * Similar to mouseleave/mouseenter, but for keyboard navigation.
+           *
+           * @ignore
+           */
+          'focusout.dropbutton': $.proxy(this.focusOut, this),
+
+          /**
+           * @ignore
+           */
+          'focusin.dropbutton': $.proxy(this.focusIn, this)
+        });
+    }
+    else {
+      this.$dropbutton.addClass('dropbutton-single');
+    }
+  }
+
+  /**
+   * Extend the DropButton constructor.
+   */
+  $.extend(DropButton, /** @lends Drupal.DropButton */{
+    /**
+     * Store all processed DropButtons.
+     *
+     * @type {Array.<Drupal.DropButton>}
+     */
+    dropbuttons: []
+  });
+
+  /**
+   * Extend the DropButton prototype.
+   */
+  $.extend(DropButton.prototype, /** @lends Drupal.DropButton# */{
+
+    /**
+     * Toggle the dropbutton open and closed.
+     *
+     * @param {bool} [show]
+     *   Force the dropbutton to open by passing true or to close by
+     *   passing false.
+     */
+    toggle: function (show) {
+      var isBool = typeof show === 'boolean';
+      show = isBool ? show : !this.$dropbutton.hasClass('open');
+      this.$dropbutton.toggleClass('open', show);
+    },
+
+    /**
+     * @method
+     */
+    hoverIn: function () {
+      // Clear any previous timer we were using.
+      if (this.timerID) {
+        window.clearTimeout(this.timerID);
+      }
+    },
+
+    /**
+     * @method
+     */
+    hoverOut: function () {
+      // Wait half a second before closing.
+      this.timerID = window.setTimeout($.proxy(this, 'close'), 500);
+    },
+
+    /**
+     * @method
+     */
+    open: function () {
+      this.toggle(true);
+    },
+
+    /**
+     * @method
+     */
+    close: function () {
+      this.toggle(false);
+    },
+
+    /**
+     * @param {jQuery.Event} e
+     *   The event triggered.
+     */
+    focusOut: function (e) {
+      this.hoverOut.call(this, e);
+    },
+
+    /**
+     * @param {jQuery.Event} e
+     *   The event triggered.
+     */
+    focusIn: function (e) {
+      this.hoverIn.call(this, e);
+    }
+  });
+
+  $.extend(Drupal.theme, /** @lends Drupal.theme */{
+
+    /**
+     * A toggle is an interactive element often bound to a click handler.
+     *
+     * @param {object} options
+     *   Options object.
+     * @param {string} [options.title]
+     *   The HTML anchor title attribute and text for the inner span element.
+     *
+     * @return {string}
+     *   A string representing a DOM fragment.
+     */
+    dropbuttonToggle: function (options) {
+      return '<li class="dropbutton-toggle"><button type="button"><span class="dropbutton-arrow"><span class="visually-hidden">' + options.title + '</span></span></button></li>';
+    }
+  });
+
+  // Expose constructor in the public space.
+  Drupal.DropButton = DropButton;
+
+})(jQuery, Drupal);
diff --git a/core/misc/dropbutton/dropbutton.js b/core/misc/dropbutton/dropbutton.js
index 04f9491ad739..43ccda4552b7 100644
--- a/core/misc/dropbutton/dropbutton.js
+++ b/core/misc/dropbutton/dropbutton.js
@@ -1,30 +1,24 @@
 /**
- * @file
- * Dropbutton feature.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./misc/dropbutton/dropbutton.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   * Process elements with the .dropbutton class on page load.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches dropButton behaviors.
-   */
   Drupal.behaviors.dropButton = {
-    attach: function (context, settings) {
+    attach: function attach(context, settings) {
       var $dropbuttons = $(context).find('.dropbutton-wrapper').once('dropbutton');
       if ($dropbuttons.length) {
-        // Adds the delegated handler that will toggle dropdowns on click.
         var $body = $('body').once('dropbutton-click');
         if ($body.length) {
           $body.on('click', '.dropbutton-toggle', dropbuttonClickHandler);
         }
-        // Initialize all buttons.
+
         var il = $dropbuttons.length;
         for (var i = 0; i < il; i++) {
           DropButton.dropbuttons.push(new DropButton($dropbuttons[i], settings.dropbutton));
@@ -33,201 +27,86 @@
     }
   };
 
-  /**
-   * Delegated callback for opening and closing dropbutton secondary actions.
-   *
-   * @function Drupal.DropButton~dropbuttonClickHandler
-   *
-   * @param {jQuery.Event} e
-   *   The event triggered.
-   */
   function dropbuttonClickHandler(e) {
     e.preventDefault();
     $(e.target).closest('.dropbutton-wrapper').toggleClass('open');
   }
 
-  /**
-   * A DropButton presents an HTML list as a button with a primary action.
-   *
-   * All secondary actions beyond the first in the list are presented in a
-   * dropdown list accessible through a toggle arrow associated with the button.
-   *
-   * @constructor Drupal.DropButton
-   *
-   * @param {HTMLElement} dropbutton
-   *   A DOM element.
-   * @param {object} settings
-   *   A list of options including:
-   * @param {string} settings.title
-   *   The text inside the toggle link element. This text is hidden
-   *   from visual UAs.
-   */
   function DropButton(dropbutton, settings) {
-    // Merge defaults with settings.
-    var options = $.extend({title: Drupal.t('List additional actions')}, settings);
+    var options = $.extend({ title: Drupal.t('List additional actions') }, settings);
     var $dropbutton = $(dropbutton);
 
-    /**
-     * @type {jQuery}
-     */
     this.$dropbutton = $dropbutton;
 
-    /**
-     * @type {jQuery}
-     */
     this.$list = $dropbutton.find('.dropbutton');
 
-    /**
-     * Find actions and mark them.
-     *
-     * @type {jQuery}
-     */
     this.$actions = this.$list.find('li').addClass('dropbutton-action');
 
-    // Add the special dropdown only if there are hidden actions.
     if (this.$actions.length > 1) {
-      // Identify the first element of the collection.
       var $primary = this.$actions.slice(0, 1);
-      // Identify the secondary actions.
+
       var $secondary = this.$actions.slice(1);
       $secondary.addClass('secondary-action');
-      // Add toggle link.
+
       $primary.after(Drupal.theme('dropbuttonToggle', options));
-      // Bind mouse events.
-      this.$dropbutton
-        .addClass('dropbutton-multiple')
-        .on({
-
-          /**
-           * Adds a timeout to close the dropdown on mouseleave.
-           *
-           * @ignore
-           */
-          'mouseleave.dropbutton': $.proxy(this.hoverOut, this),
-
-          /**
-           * Clears timeout when mouseout of the dropdown.
-           *
-           * @ignore
-           */
-          'mouseenter.dropbutton': $.proxy(this.hoverIn, this),
-
-          /**
-           * Similar to mouseleave/mouseenter, but for keyboard navigation.
-           *
-           * @ignore
-           */
-          'focusout.dropbutton': $.proxy(this.focusOut, this),
-
-          /**
-           * @ignore
-           */
-          'focusin.dropbutton': $.proxy(this.focusIn, this)
-        });
-    }
-    else {
+
+      this.$dropbutton.addClass('dropbutton-multiple').on({
+        'mouseleave.dropbutton': $.proxy(this.hoverOut, this),
+
+        'mouseenter.dropbutton': $.proxy(this.hoverIn, this),
+
+        'focusout.dropbutton': $.proxy(this.focusOut, this),
+
+        'focusin.dropbutton': $.proxy(this.focusIn, this)
+      });
+    } else {
       this.$dropbutton.addClass('dropbutton-single');
     }
   }
 
-  /**
-   * Extend the DropButton constructor.
-   */
-  $.extend(DropButton, /** @lends Drupal.DropButton */{
-    /**
-     * Store all processed DropButtons.
-     *
-     * @type {Array.<Drupal.DropButton>}
-     */
+  $.extend(DropButton, {
     dropbuttons: []
   });
 
-  /**
-   * Extend the DropButton prototype.
-   */
-  $.extend(DropButton.prototype, /** @lends Drupal.DropButton# */{
-
-    /**
-     * Toggle the dropbutton open and closed.
-     *
-     * @param {bool} [show]
-     *   Force the dropbutton to open by passing true or to close by
-     *   passing false.
-     */
-    toggle: function (show) {
+  $.extend(DropButton.prototype, {
+    toggle: function toggle(show) {
       var isBool = typeof show === 'boolean';
       show = isBool ? show : !this.$dropbutton.hasClass('open');
       this.$dropbutton.toggleClass('open', show);
     },
 
-    /**
-     * @method
-     */
-    hoverIn: function () {
-      // Clear any previous timer we were using.
+    hoverIn: function hoverIn() {
       if (this.timerID) {
         window.clearTimeout(this.timerID);
       }
     },
 
-    /**
-     * @method
-     */
-    hoverOut: function () {
-      // Wait half a second before closing.
+    hoverOut: function hoverOut() {
       this.timerID = window.setTimeout($.proxy(this, 'close'), 500);
     },
 
-    /**
-     * @method
-     */
-    open: function () {
+    open: function open() {
       this.toggle(true);
     },
 
-    /**
-     * @method
-     */
-    close: function () {
+    close: function close() {
       this.toggle(false);
     },
 
-    /**
-     * @param {jQuery.Event} e
-     *   The event triggered.
-     */
-    focusOut: function (e) {
+    focusOut: function focusOut(e) {
       this.hoverOut.call(this, e);
     },
 
-    /**
-     * @param {jQuery.Event} e
-     *   The event triggered.
-     */
-    focusIn: function (e) {
+    focusIn: function focusIn(e) {
       this.hoverIn.call(this, e);
     }
   });
 
-  $.extend(Drupal.theme, /** @lends Drupal.theme */{
-
-    /**
-     * A toggle is an interactive element often bound to a click handler.
-     *
-     * @param {object} options
-     *   Options object.
-     * @param {string} [options.title]
-     *   The HTML anchor title attribute and text for the inner span element.
-     *
-     * @return {string}
-     *   A string representing a DOM fragment.
-     */
-    dropbuttonToggle: function (options) {
+  $.extend(Drupal.theme, {
+    dropbuttonToggle: function dropbuttonToggle(options) {
       return '<li class="dropbutton-toggle"><button type="button"><span class="dropbutton-arrow"><span class="visually-hidden">' + options.title + '</span></span></button></li>';
     }
   });
 
-  // Expose constructor in the public space.
   Drupal.DropButton = DropButton;
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/misc/drupal.es6.js b/core/misc/drupal.es6.js
new file mode 100644
index 000000000000..d509795b864e
--- /dev/null
+++ b/core/misc/drupal.es6.js
@@ -0,0 +1,583 @@
+/**
+ * @file
+ * Defines the Drupal JavaScript API.
+ */
+
+/**
+ * A jQuery object, typically the return value from a `$(selector)` call.
+ *
+ * Holds an HTMLElement or a collection of HTMLElements.
+ *
+ * @typedef {object} jQuery
+ *
+ * @prop {number} length=0
+ *   Number of elements contained in the jQuery object.
+ */
+
+/**
+ * Variable generated by Drupal that holds all translated strings from PHP.
+ *
+ * Content of this variable is automatically created by Drupal when using the
+ * Interface Translation module. It holds the translation of strings used on
+ * the page.
+ *
+ * This variable is used to pass data from the backend to the frontend. Data
+ * contained in `drupalSettings` is used during behavior initialization.
+ *
+ * @global
+ *
+ * @var {object} drupalTranslations
+ */
+
+/**
+ * Global Drupal object.
+ *
+ * All Drupal JavaScript APIs are contained in this namespace.
+ *
+ * @global
+ *
+ * @namespace
+ */
+window.Drupal = {behaviors: {}, locale: {}};
+
+// JavaScript should be made compatible with libraries other than jQuery by
+// wrapping it in an anonymous closure.
+(function (Drupal, drupalSettings, drupalTranslations) {
+
+  'use strict';
+
+  /**
+   * Helper to rethrow errors asynchronously.
+   *
+   * This way Errors bubbles up outside of the original callstack, making it
+   * easier to debug errors in the browser.
+   *
+   * @param {Error|string} error
+   *   The error to be thrown.
+   */
+  Drupal.throwError = function (error) {
+    setTimeout(function () { throw error; }, 0);
+  };
+
+  /**
+   * Custom error thrown after attach/detach if one or more behaviors failed.
+   * Initializes the JavaScript behaviors for page loads and Ajax requests.
+   *
+   * @callback Drupal~behaviorAttach
+   *
+   * @param {HTMLDocument|HTMLElement} context
+   *   An element to detach behaviors from.
+   * @param {?object} settings
+   *   An object containing settings for the current context. It is rarely used.
+   *
+   * @see Drupal.attachBehaviors
+   */
+
+  /**
+   * Reverts and cleans up JavaScript behavior initialization.
+   *
+   * @callback Drupal~behaviorDetach
+   *
+   * @param {HTMLDocument|HTMLElement} context
+   *   An element to attach behaviors to.
+   * @param {object} settings
+   *   An object containing settings for the current context.
+   * @param {string} trigger
+   *   One of `'unload'`, `'move'`, or `'serialize'`.
+   *
+   * @see Drupal.detachBehaviors
+   */
+
+  /**
+   * @typedef {object} Drupal~behavior
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Function run on page load and after an Ajax call.
+   * @prop {Drupal~behaviorDetach} detach
+   *   Function run when content is serialized or removed from the page.
+   */
+
+  /**
+   * Holds all initialization methods.
+   *
+   * @namespace Drupal.behaviors
+   *
+   * @type {Object.<string, Drupal~behavior>}
+   */
+
+  /**
+   * Defines a behavior to be run during attach and detach phases.
+   *
+   * Attaches all registered behaviors to a page element.
+   *
+   * Behaviors are event-triggered actions that attach to page elements,
+   * enhancing default non-JavaScript UIs. Behaviors are registered in the
+   * {@link Drupal.behaviors} object using the method 'attach' and optionally
+   * also 'detach'.
+   *
+   * {@link Drupal.attachBehaviors} is added below to the `jQuery.ready` event
+   * and therefore runs on initial page load. Developers implementing Ajax in
+   * their solutions should also call this function after new page content has
+   * been loaded, feeding in an element to be processed, in order to attach all
+   * behaviors to the new content.
+   *
+   * Behaviors should use `var elements =
+   * $(context).find(selector).once('behavior-name');` to ensure the behavior is
+   * attached only once to a given element. (Doing so enables the reprocessing
+   * of given elements, which may be needed on occasion despite the ability to
+   * limit behavior attachment to a particular element.)
+   *
+   * @example
+   * Drupal.behaviors.behaviorName = {
+   *   attach: function (context, settings) {
+   *     // ...
+   *   },
+   *   detach: function (context, settings, trigger) {
+   *     // ...
+   *   }
+   * };
+   *
+   * @param {HTMLDocument|HTMLElement} [context=document]
+   *   An element to attach behaviors to.
+   * @param {object} [settings=drupalSettings]
+   *   An object containing settings for the current context. If none is given,
+   *   the global {@link drupalSettings} object is used.
+   *
+   * @see Drupal~behaviorAttach
+   * @see Drupal.detachBehaviors
+   *
+   * @throws {Drupal~DrupalBehaviorError}
+   */
+  Drupal.attachBehaviors = function (context, settings) {
+    context = context || document;
+    settings = settings || drupalSettings;
+    var behaviors = Drupal.behaviors;
+    // Execute all of them.
+    for (var i in behaviors) {
+      if (behaviors.hasOwnProperty(i) && typeof behaviors[i].attach === 'function') {
+        // Don't stop the execution of behaviors in case of an error.
+        try {
+          behaviors[i].attach(context, settings);
+        }
+        catch (e) {
+          Drupal.throwError(e);
+        }
+      }
+    }
+  };
+
+  /**
+   * Detaches registered behaviors from a page element.
+   *
+   * Developers implementing Ajax in their solutions should call this function
+   * before page content is about to be removed, feeding in an element to be
+   * processed, in order to allow special behaviors to detach from the content.
+   *
+   * Such implementations should use `.findOnce()` and `.removeOnce()` to find
+   * elements with their corresponding `Drupal.behaviors.behaviorName.attach`
+   * implementation, i.e. `.removeOnce('behaviorName')`, to ensure the behavior
+   * is detached only from previously processed elements.
+   *
+   * @param {HTMLDocument|HTMLElement} [context=document]
+   *   An element to detach behaviors from.
+   * @param {object} [settings=drupalSettings]
+   *   An object containing settings for the current context. If none given,
+   *   the global {@link drupalSettings} object is used.
+   * @param {string} [trigger='unload']
+   *   A string containing what's causing the behaviors to be detached. The
+   *   possible triggers are:
+   *   - `'unload'`: The context element is being removed from the DOM.
+   *   - `'move'`: The element is about to be moved within the DOM (for example,
+   *     during a tabledrag row swap). After the move is completed,
+   *     {@link Drupal.attachBehaviors} is called, so that the behavior can undo
+   *     whatever it did in response to the move. Many behaviors won't need to
+   *     do anything simply in response to the element being moved, but because
+   *     IFRAME elements reload their "src" when being moved within the DOM,
+   *     behaviors bound to IFRAME elements (like WYSIWYG editors) may need to
+   *     take some action.
+   *   - `'serialize'`: When an Ajax form is submitted, this is called with the
+   *     form as the context. This provides every behavior within the form an
+   *     opportunity to ensure that the field elements have correct content
+   *     in them before the form is serialized. The canonical use-case is so
+   *     that WYSIWYG editors can update the hidden textarea to which they are
+   *     bound.
+   *
+   * @throws {Drupal~DrupalBehaviorError}
+   *
+   * @see Drupal~behaviorDetach
+   * @see Drupal.attachBehaviors
+   */
+  Drupal.detachBehaviors = function (context, settings, trigger) {
+    context = context || document;
+    settings = settings || drupalSettings;
+    trigger = trigger || 'unload';
+    var behaviors = Drupal.behaviors;
+    // Execute all of them.
+    for (var i in behaviors) {
+      if (behaviors.hasOwnProperty(i) && typeof behaviors[i].detach === 'function') {
+        // Don't stop the execution of behaviors in case of an error.
+        try {
+          behaviors[i].detach(context, settings, trigger);
+        }
+        catch (e) {
+          Drupal.throwError(e);
+        }
+      }
+    }
+  };
+
+  /**
+   * Encodes special characters in a plain-text string for display as HTML.
+   *
+   * @param {string} str
+   *   The string to be encoded.
+   *
+   * @return {string}
+   *   The encoded string.
+   *
+   * @ingroup sanitization
+   */
+  Drupal.checkPlain = function (str) {
+    str = str.toString()
+      .replace(/&/g, '&amp;')
+      .replace(/"/g, '&quot;')
+      .replace(/</g, '&lt;')
+      .replace(/>/g, '&gt;');
+    return str;
+  };
+
+  /**
+   * Replaces placeholders with sanitized values in a string.
+   *
+   * @param {string} str
+   *   A string with placeholders.
+   * @param {object} args
+   *   An object of replacements pairs to make. Incidences of any key in this
+   *   array are replaced with the corresponding value. Based on the first
+   *   character of the key, the value is escaped and/or themed:
+   *    - `'!variable'`: inserted as is.
+   *    - `'@variable'`: escape plain text to HTML ({@link Drupal.checkPlain}).
+   *    - `'%variable'`: escape text and theme as a placeholder for user-
+   *      submitted content ({@link Drupal.checkPlain} +
+   *      `{@link Drupal.theme}('placeholder')`).
+   *
+   * @return {string}
+   *   The formatted string.
+   *
+   * @see Drupal.t
+   */
+  Drupal.formatString = function (str, args) {
+    // Keep args intact.
+    var processedArgs = {};
+    // Transform arguments before inserting them.
+    for (var key in args) {
+      if (args.hasOwnProperty(key)) {
+        switch (key.charAt(0)) {
+          // Escaped only.
+          case '@':
+            processedArgs[key] = Drupal.checkPlain(args[key]);
+            break;
+
+          // Pass-through.
+          case '!':
+            processedArgs[key] = args[key];
+            break;
+
+          // Escaped and placeholder.
+          default:
+            processedArgs[key] = Drupal.theme('placeholder', args[key]);
+            break;
+        }
+      }
+    }
+
+    return Drupal.stringReplace(str, processedArgs, null);
+  };
+
+  /**
+   * Replaces substring.
+   *
+   * The longest keys will be tried first. Once a substring has been replaced,
+   * its new value will not be searched again.
+   *
+   * @param {string} str
+   *   A string with placeholders.
+   * @param {object} args
+   *   Key-value pairs.
+   * @param {Array|null} keys
+   *   Array of keys from `args`. Internal use only.
+   *
+   * @return {string}
+   *   The replaced string.
+   */
+  Drupal.stringReplace = function (str, args, keys) {
+    if (str.length === 0) {
+      return str;
+    }
+
+    // If the array of keys is not passed then collect the keys from the args.
+    if (!Array.isArray(keys)) {
+      keys = [];
+      for (var k in args) {
+        if (args.hasOwnProperty(k)) {
+          keys.push(k);
+        }
+      }
+
+      // Order the keys by the character length. The shortest one is the first.
+      keys.sort(function (a, b) { return a.length - b.length; });
+    }
+
+    if (keys.length === 0) {
+      return str;
+    }
+
+    // Take next longest one from the end.
+    var key = keys.pop();
+    var fragments = str.split(key);
+
+    if (keys.length) {
+      for (var i = 0; i < fragments.length; i++) {
+        // Process each fragment with a copy of remaining keys.
+        fragments[i] = Drupal.stringReplace(fragments[i], args, keys.slice(0));
+      }
+    }
+
+    return fragments.join(args[key]);
+  };
+
+  /**
+   * Translates strings to the page language, or a given language.
+   *
+   * See the documentation of the server-side t() function for further details.
+   *
+   * @param {string} str
+   *   A string containing the English text to translate.
+   * @param {Object.<string, string>} [args]
+   *   An object of replacements pairs to make after translation. Incidences
+   *   of any key in this array are replaced with the corresponding value.
+   *   See {@link Drupal.formatString}.
+   * @param {object} [options]
+   *   Additional options for translation.
+   * @param {string} [options.context='']
+   *   The context the source string belongs to.
+   *
+   * @return {string}
+   *   The formatted string.
+   *   The translated string.
+   */
+  Drupal.t = function (str, args, options) {
+    options = options || {};
+    options.context = options.context || '';
+
+    // Fetch the localized version of the string.
+    if (typeof drupalTranslations !== 'undefined' && drupalTranslations.strings && drupalTranslations.strings[options.context] && drupalTranslations.strings[options.context][str]) {
+      str = drupalTranslations.strings[options.context][str];
+    }
+
+    if (args) {
+      str = Drupal.formatString(str, args);
+    }
+    return str;
+  };
+
+  /**
+   * Returns the URL to a Drupal page.
+   *
+   * @param {string} path
+   *   Drupal path to transform to URL.
+   *
+   * @return {string}
+   *   The full URL.
+   */
+  Drupal.url = function (path) {
+    return drupalSettings.path.baseUrl + drupalSettings.path.pathPrefix + path;
+  };
+
+  /**
+   * Returns the passed in URL as an absolute URL.
+   *
+   * @param {string} url
+   *   The URL string to be normalized to an absolute URL.
+   *
+   * @return {string}
+   *   The normalized, absolute URL.
+   *
+   * @see https://github.com/angular/angular.js/blob/v1.4.4/src/ng/urlUtils.js
+   * @see https://grack.com/blog/2009/11/17/absolutizing-url-in-javascript
+   * @see https://github.com/jquery/jquery-ui/blob/1.11.4/ui/tabs.js#L53
+   */
+  Drupal.url.toAbsolute = function (url) {
+    var urlParsingNode = document.createElement('a');
+
+    // Decode the URL first; this is required by IE <= 6. Decoding non-UTF-8
+    // strings may throw an exception.
+    try {
+      url = decodeURIComponent(url);
+    }
+    catch (e) {
+      // Empty.
+    }
+
+    urlParsingNode.setAttribute('href', url);
+
+    // IE <= 7 normalizes the URL when assigned to the anchor node similar to
+    // the other browsers.
+    return urlParsingNode.cloneNode(false).href;
+  };
+
+  /**
+   * Returns true if the URL is within Drupal's base path.
+   *
+   * @param {string} url
+   *   The URL string to be tested.
+   *
+   * @return {bool}
+   *   `true` if local.
+   *
+   * @see https://github.com/jquery/jquery-ui/blob/1.11.4/ui/tabs.js#L58
+   */
+  Drupal.url.isLocal = function (url) {
+    // Always use browser-derived absolute URLs in the comparison, to avoid
+    // attempts to break out of the base path using directory traversal.
+    var absoluteUrl = Drupal.url.toAbsolute(url);
+    var protocol = location.protocol;
+
+    // Consider URLs that match this site's base URL but use HTTPS instead of HTTP
+    // as local as well.
+    if (protocol === 'http:' && absoluteUrl.indexOf('https:') === 0) {
+      protocol = 'https:';
+    }
+    var baseUrl = protocol + '//' + location.host + drupalSettings.path.baseUrl.slice(0, -1);
+
+    // Decoding non-UTF-8 strings may throw an exception.
+    try {
+      absoluteUrl = decodeURIComponent(absoluteUrl);
+    }
+    catch (e) {
+      // Empty.
+    }
+    try {
+      baseUrl = decodeURIComponent(baseUrl);
+    }
+    catch (e) {
+      // Empty.
+    }
+
+    // The given URL matches the site's base URL, or has a path under the site's
+    // base URL.
+    return absoluteUrl === baseUrl || absoluteUrl.indexOf(baseUrl + '/') === 0;
+  };
+
+  /**
+   * Formats a string containing a count of items.
+   *
+   * This function ensures that the string is pluralized correctly. Since
+   * {@link Drupal.t} is called by this function, make sure not to pass
+   * already-localized strings to it.
+   *
+   * See the documentation of the server-side
+   * \Drupal\Core\StringTranslation\TranslationInterface::formatPlural()
+   * function for more details.
+   *
+   * @param {number} count
+   *   The item count to display.
+   * @param {string} singular
+   *   The string for the singular case. Please make sure it is clear this is
+   *   singular, to ease translation (e.g. use "1 new comment" instead of "1
+   *   new"). Do not use @count in the singular string.
+   * @param {string} plural
+   *   The string for the plural case. Please make sure it is clear this is
+   *   plural, to ease translation. Use @count in place of the item count, as in
+   *   "@count new comments".
+   * @param {object} [args]
+   *   An object of replacements pairs to make after translation. Incidences
+   *   of any key in this array are replaced with the corresponding value.
+   *   See {@link Drupal.formatString}.
+   *   Note that you do not need to include @count in this array.
+   *   This replacement is done automatically for the plural case.
+   * @param {object} [options]
+   *   The options to pass to the {@link Drupal.t} function.
+   *
+   * @return {string}
+   *   A translated string.
+   */
+  Drupal.formatPlural = function (count, singular, plural, args, options) {
+    args = args || {};
+    args['@count'] = count;
+
+    var pluralDelimiter = drupalSettings.pluralDelimiter;
+    var translations = Drupal.t(singular + pluralDelimiter + plural, args, options).split(pluralDelimiter);
+    var index = 0;
+
+    // Determine the index of the plural form.
+    if (typeof drupalTranslations !== 'undefined' && drupalTranslations.pluralFormula) {
+      index = count in drupalTranslations.pluralFormula ? drupalTranslations.pluralFormula[count] : drupalTranslations.pluralFormula['default'];
+    }
+    else if (args['@count'] !== 1) {
+      index = 1;
+    }
+
+    return translations[index];
+  };
+
+  /**
+   * Encodes a Drupal path for use in a URL.
+   *
+   * For aesthetic reasons slashes are not escaped.
+   *
+   * @param {string} item
+   *   Unencoded path.
+   *
+   * @return {string}
+   *   The encoded path.
+   */
+  Drupal.encodePath = function (item) {
+    return window.encodeURIComponent(item).replace(/%2F/g, '/');
+  };
+
+  /**
+   * Generates the themed representation of a Drupal object.
+   *
+   * All requests for themed output must go through this function. It examines
+   * the request and routes it to the appropriate theme function. If the current
+   * theme does not provide an override function, the generic theme function is
+   * called.
+   *
+   * @example
+   * <caption>To retrieve the HTML for text that should be emphasized and
+   * displayed as a placeholder inside a sentence.</caption>
+   * Drupal.theme('placeholder', text);
+   *
+   * @namespace
+   *
+   * @param {function} func
+   *   The name of the theme function to call.
+   * @param {...args}
+   *   Additional arguments to pass along to the theme function.
+   *
+   * @return {string|object|HTMLElement|jQuery}
+   *   Any data the theme function returns. This could be a plain HTML string,
+   *   but also a complex object.
+   */
+  Drupal.theme = function (func) {
+    var args = Array.prototype.slice.apply(arguments, [1]);
+    if (func in Drupal.theme) {
+      return Drupal.theme[func].apply(this, args);
+    }
+  };
+
+  /**
+   * Formats text for emphasized display in a placeholder inside a sentence.
+   *
+   * @param {string} str
+   *   The text to format (plain-text).
+   *
+   * @return {string}
+   *   The formatted text (html).
+   */
+  Drupal.theme.placeholder = function (str) {
+    return '<em class="placeholder">' + Drupal.checkPlain(str) + '</em>';
+  };
+
+})(Drupal, window.drupalSettings, window.drupalTranslations);
diff --git a/core/misc/drupal.init.es6.js b/core/misc/drupal.init.es6.js
new file mode 100644
index 000000000000..0e55e190efa5
--- /dev/null
+++ b/core/misc/drupal.init.es6.js
@@ -0,0 +1,19 @@
+// Allow other JavaScript libraries to use $.
+if (window.jQuery) {
+  jQuery.noConflict();
+}
+
+// Class indicating that JS is enabled; used for styling purpose.
+document.documentElement.className += ' js';
+
+// JavaScript should be made compatible with libraries other than jQuery by
+// wrapping it in an anonymous closure.
+
+(function (domready, Drupal, drupalSettings) {
+
+  'use strict';
+
+  // Attach all behaviors.
+  domready(function () { Drupal.attachBehaviors(document, drupalSettings); });
+
+})(domready, Drupal, window.drupalSettings);
diff --git a/core/misc/drupal.init.js b/core/misc/drupal.init.js
index 0e55e190efa5..b9331579eaf4 100644
--- a/core/misc/drupal.init.js
+++ b/core/misc/drupal.init.js
@@ -1,19 +1,22 @@
-// Allow other JavaScript libraries to use $.
+/**
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./misc/drupal.init.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
+
 if (window.jQuery) {
   jQuery.noConflict();
 }
 
-// Class indicating that JS is enabled; used for styling purpose.
 document.documentElement.className += ' js';
 
-// JavaScript should be made compatible with libraries other than jQuery by
-// wrapping it in an anonymous closure.
-
 (function (domready, Drupal, drupalSettings) {
 
   'use strict';
 
-  // Attach all behaviors.
-  domready(function () { Drupal.attachBehaviors(document, drupalSettings); });
-
-})(domready, Drupal, window.drupalSettings);
+  domready(function () {
+    Drupal.attachBehaviors(document, drupalSettings);
+  });
+})(domready, Drupal, window.drupalSettings);
\ No newline at end of file
diff --git a/core/misc/drupal.js b/core/misc/drupal.js
index d509795b864e..12b2e457046e 100644
--- a/core/misc/drupal.js
+++ b/core/misc/drupal.js
@@ -1,289 +1,75 @@
 /**
- * @file
- * Defines the Drupal JavaScript API.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./misc/drupal.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
-/**
- * A jQuery object, typically the return value from a `$(selector)` call.
- *
- * Holds an HTMLElement or a collection of HTMLElements.
- *
- * @typedef {object} jQuery
- *
- * @prop {number} length=0
- *   Number of elements contained in the jQuery object.
- */
-
-/**
- * Variable generated by Drupal that holds all translated strings from PHP.
- *
- * Content of this variable is automatically created by Drupal when using the
- * Interface Translation module. It holds the translation of strings used on
- * the page.
- *
- * This variable is used to pass data from the backend to the frontend. Data
- * contained in `drupalSettings` is used during behavior initialization.
- *
- * @global
- *
- * @var {object} drupalTranslations
- */
+window.Drupal = { behaviors: {}, locale: {} };
 
-/**
- * Global Drupal object.
- *
- * All Drupal JavaScript APIs are contained in this namespace.
- *
- * @global
- *
- * @namespace
- */
-window.Drupal = {behaviors: {}, locale: {}};
-
-// JavaScript should be made compatible with libraries other than jQuery by
-// wrapping it in an anonymous closure.
 (function (Drupal, drupalSettings, drupalTranslations) {
 
   'use strict';
 
-  /**
-   * Helper to rethrow errors asynchronously.
-   *
-   * This way Errors bubbles up outside of the original callstack, making it
-   * easier to debug errors in the browser.
-   *
-   * @param {Error|string} error
-   *   The error to be thrown.
-   */
   Drupal.throwError = function (error) {
-    setTimeout(function () { throw error; }, 0);
+    setTimeout(function () {
+      throw error;
+    }, 0);
   };
 
-  /**
-   * Custom error thrown after attach/detach if one or more behaviors failed.
-   * Initializes the JavaScript behaviors for page loads and Ajax requests.
-   *
-   * @callback Drupal~behaviorAttach
-   *
-   * @param {HTMLDocument|HTMLElement} context
-   *   An element to detach behaviors from.
-   * @param {?object} settings
-   *   An object containing settings for the current context. It is rarely used.
-   *
-   * @see Drupal.attachBehaviors
-   */
-
-  /**
-   * Reverts and cleans up JavaScript behavior initialization.
-   *
-   * @callback Drupal~behaviorDetach
-   *
-   * @param {HTMLDocument|HTMLElement} context
-   *   An element to attach behaviors to.
-   * @param {object} settings
-   *   An object containing settings for the current context.
-   * @param {string} trigger
-   *   One of `'unload'`, `'move'`, or `'serialize'`.
-   *
-   * @see Drupal.detachBehaviors
-   */
-
-  /**
-   * @typedef {object} Drupal~behavior
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Function run on page load and after an Ajax call.
-   * @prop {Drupal~behaviorDetach} detach
-   *   Function run when content is serialized or removed from the page.
-   */
-
-  /**
-   * Holds all initialization methods.
-   *
-   * @namespace Drupal.behaviors
-   *
-   * @type {Object.<string, Drupal~behavior>}
-   */
-
-  /**
-   * Defines a behavior to be run during attach and detach phases.
-   *
-   * Attaches all registered behaviors to a page element.
-   *
-   * Behaviors are event-triggered actions that attach to page elements,
-   * enhancing default non-JavaScript UIs. Behaviors are registered in the
-   * {@link Drupal.behaviors} object using the method 'attach' and optionally
-   * also 'detach'.
-   *
-   * {@link Drupal.attachBehaviors} is added below to the `jQuery.ready` event
-   * and therefore runs on initial page load. Developers implementing Ajax in
-   * their solutions should also call this function after new page content has
-   * been loaded, feeding in an element to be processed, in order to attach all
-   * behaviors to the new content.
-   *
-   * Behaviors should use `var elements =
-   * $(context).find(selector).once('behavior-name');` to ensure the behavior is
-   * attached only once to a given element. (Doing so enables the reprocessing
-   * of given elements, which may be needed on occasion despite the ability to
-   * limit behavior attachment to a particular element.)
-   *
-   * @example
-   * Drupal.behaviors.behaviorName = {
-   *   attach: function (context, settings) {
-   *     // ...
-   *   },
-   *   detach: function (context, settings, trigger) {
-   *     // ...
-   *   }
-   * };
-   *
-   * @param {HTMLDocument|HTMLElement} [context=document]
-   *   An element to attach behaviors to.
-   * @param {object} [settings=drupalSettings]
-   *   An object containing settings for the current context. If none is given,
-   *   the global {@link drupalSettings} object is used.
-   *
-   * @see Drupal~behaviorAttach
-   * @see Drupal.detachBehaviors
-   *
-   * @throws {Drupal~DrupalBehaviorError}
-   */
   Drupal.attachBehaviors = function (context, settings) {
     context = context || document;
     settings = settings || drupalSettings;
     var behaviors = Drupal.behaviors;
-    // Execute all of them.
+
     for (var i in behaviors) {
       if (behaviors.hasOwnProperty(i) && typeof behaviors[i].attach === 'function') {
-        // Don't stop the execution of behaviors in case of an error.
         try {
           behaviors[i].attach(context, settings);
-        }
-        catch (e) {
+        } catch (e) {
           Drupal.throwError(e);
         }
       }
     }
   };
 
-  /**
-   * Detaches registered behaviors from a page element.
-   *
-   * Developers implementing Ajax in their solutions should call this function
-   * before page content is about to be removed, feeding in an element to be
-   * processed, in order to allow special behaviors to detach from the content.
-   *
-   * Such implementations should use `.findOnce()` and `.removeOnce()` to find
-   * elements with their corresponding `Drupal.behaviors.behaviorName.attach`
-   * implementation, i.e. `.removeOnce('behaviorName')`, to ensure the behavior
-   * is detached only from previously processed elements.
-   *
-   * @param {HTMLDocument|HTMLElement} [context=document]
-   *   An element to detach behaviors from.
-   * @param {object} [settings=drupalSettings]
-   *   An object containing settings for the current context. If none given,
-   *   the global {@link drupalSettings} object is used.
-   * @param {string} [trigger='unload']
-   *   A string containing what's causing the behaviors to be detached. The
-   *   possible triggers are:
-   *   - `'unload'`: The context element is being removed from the DOM.
-   *   - `'move'`: The element is about to be moved within the DOM (for example,
-   *     during a tabledrag row swap). After the move is completed,
-   *     {@link Drupal.attachBehaviors} is called, so that the behavior can undo
-   *     whatever it did in response to the move. Many behaviors won't need to
-   *     do anything simply in response to the element being moved, but because
-   *     IFRAME elements reload their "src" when being moved within the DOM,
-   *     behaviors bound to IFRAME elements (like WYSIWYG editors) may need to
-   *     take some action.
-   *   - `'serialize'`: When an Ajax form is submitted, this is called with the
-   *     form as the context. This provides every behavior within the form an
-   *     opportunity to ensure that the field elements have correct content
-   *     in them before the form is serialized. The canonical use-case is so
-   *     that WYSIWYG editors can update the hidden textarea to which they are
-   *     bound.
-   *
-   * @throws {Drupal~DrupalBehaviorError}
-   *
-   * @see Drupal~behaviorDetach
-   * @see Drupal.attachBehaviors
-   */
   Drupal.detachBehaviors = function (context, settings, trigger) {
     context = context || document;
     settings = settings || drupalSettings;
     trigger = trigger || 'unload';
     var behaviors = Drupal.behaviors;
-    // Execute all of them.
+
     for (var i in behaviors) {
       if (behaviors.hasOwnProperty(i) && typeof behaviors[i].detach === 'function') {
-        // Don't stop the execution of behaviors in case of an error.
         try {
           behaviors[i].detach(context, settings, trigger);
-        }
-        catch (e) {
+        } catch (e) {
           Drupal.throwError(e);
         }
       }
     }
   };
 
-  /**
-   * Encodes special characters in a plain-text string for display as HTML.
-   *
-   * @param {string} str
-   *   The string to be encoded.
-   *
-   * @return {string}
-   *   The encoded string.
-   *
-   * @ingroup sanitization
-   */
   Drupal.checkPlain = function (str) {
-    str = str.toString()
-      .replace(/&/g, '&amp;')
-      .replace(/"/g, '&quot;')
-      .replace(/</g, '&lt;')
-      .replace(/>/g, '&gt;');
+    str = str.toString().replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
     return str;
   };
 
-  /**
-   * Replaces placeholders with sanitized values in a string.
-   *
-   * @param {string} str
-   *   A string with placeholders.
-   * @param {object} args
-   *   An object of replacements pairs to make. Incidences of any key in this
-   *   array are replaced with the corresponding value. Based on the first
-   *   character of the key, the value is escaped and/or themed:
-   *    - `'!variable'`: inserted as is.
-   *    - `'@variable'`: escape plain text to HTML ({@link Drupal.checkPlain}).
-   *    - `'%variable'`: escape text and theme as a placeholder for user-
-   *      submitted content ({@link Drupal.checkPlain} +
-   *      `{@link Drupal.theme}('placeholder')`).
-   *
-   * @return {string}
-   *   The formatted string.
-   *
-   * @see Drupal.t
-   */
   Drupal.formatString = function (str, args) {
-    // Keep args intact.
     var processedArgs = {};
-    // Transform arguments before inserting them.
+
     for (var key in args) {
       if (args.hasOwnProperty(key)) {
         switch (key.charAt(0)) {
-          // Escaped only.
           case '@':
             processedArgs[key] = Drupal.checkPlain(args[key]);
             break;
 
-          // Pass-through.
           case '!':
             processedArgs[key] = args[key];
             break;
 
-          // Escaped and placeholder.
           default:
             processedArgs[key] = Drupal.theme('placeholder', args[key]);
             break;
@@ -294,28 +80,11 @@ window.Drupal = {behaviors: {}, locale: {}};
     return Drupal.stringReplace(str, processedArgs, null);
   };
 
-  /**
-   * Replaces substring.
-   *
-   * The longest keys will be tried first. Once a substring has been replaced,
-   * its new value will not be searched again.
-   *
-   * @param {string} str
-   *   A string with placeholders.
-   * @param {object} args
-   *   Key-value pairs.
-   * @param {Array|null} keys
-   *   Array of keys from `args`. Internal use only.
-   *
-   * @return {string}
-   *   The replaced string.
-   */
   Drupal.stringReplace = function (str, args, keys) {
     if (str.length === 0) {
       return str;
     }
 
-    // If the array of keys is not passed then collect the keys from the args.
     if (!Array.isArray(keys)) {
       keys = [];
       for (var k in args) {
@@ -324,21 +93,20 @@ window.Drupal = {behaviors: {}, locale: {}};
         }
       }
 
-      // Order the keys by the character length. The shortest one is the first.
-      keys.sort(function (a, b) { return a.length - b.length; });
+      keys.sort(function (a, b) {
+        return a.length - b.length;
+      });
     }
 
     if (keys.length === 0) {
       return str;
     }
 
-    // Take next longest one from the end.
     var key = keys.pop();
     var fragments = str.split(key);
 
     if (keys.length) {
       for (var i = 0; i < fragments.length; i++) {
-        // Process each fragment with a copy of remaining keys.
         fragments[i] = Drupal.stringReplace(fragments[i], args, keys.slice(0));
       }
     }
@@ -346,31 +114,10 @@ window.Drupal = {behaviors: {}, locale: {}};
     return fragments.join(args[key]);
   };
 
-  /**
-   * Translates strings to the page language, or a given language.
-   *
-   * See the documentation of the server-side t() function for further details.
-   *
-   * @param {string} str
-   *   A string containing the English text to translate.
-   * @param {Object.<string, string>} [args]
-   *   An object of replacements pairs to make after translation. Incidences
-   *   of any key in this array are replaced with the corresponding value.
-   *   See {@link Drupal.formatString}.
-   * @param {object} [options]
-   *   Additional options for translation.
-   * @param {string} [options.context='']
-   *   The context the source string belongs to.
-   *
-   * @return {string}
-   *   The formatted string.
-   *   The translated string.
-   */
   Drupal.t = function (str, args, options) {
     options = options || {};
     options.context = options.context || '';
 
-    // Fetch the localized version of the string.
     if (typeof drupalTranslations !== 'undefined' && drupalTranslations.strings && drupalTranslations.strings[options.context] && drupalTranslations.strings[options.context][str]) {
       str = drupalTranslations.strings[options.context][str];
     }
@@ -381,127 +128,41 @@ window.Drupal = {behaviors: {}, locale: {}};
     return str;
   };
 
-  /**
-   * Returns the URL to a Drupal page.
-   *
-   * @param {string} path
-   *   Drupal path to transform to URL.
-   *
-   * @return {string}
-   *   The full URL.
-   */
   Drupal.url = function (path) {
     return drupalSettings.path.baseUrl + drupalSettings.path.pathPrefix + path;
   };
 
-  /**
-   * Returns the passed in URL as an absolute URL.
-   *
-   * @param {string} url
-   *   The URL string to be normalized to an absolute URL.
-   *
-   * @return {string}
-   *   The normalized, absolute URL.
-   *
-   * @see https://github.com/angular/angular.js/blob/v1.4.4/src/ng/urlUtils.js
-   * @see https://grack.com/blog/2009/11/17/absolutizing-url-in-javascript
-   * @see https://github.com/jquery/jquery-ui/blob/1.11.4/ui/tabs.js#L53
-   */
   Drupal.url.toAbsolute = function (url) {
     var urlParsingNode = document.createElement('a');
 
-    // Decode the URL first; this is required by IE <= 6. Decoding non-UTF-8
-    // strings may throw an exception.
     try {
       url = decodeURIComponent(url);
-    }
-    catch (e) {
-      // Empty.
-    }
+    } catch (e) {}
 
     urlParsingNode.setAttribute('href', url);
 
-    // IE <= 7 normalizes the URL when assigned to the anchor node similar to
-    // the other browsers.
     return urlParsingNode.cloneNode(false).href;
   };
 
-  /**
-   * Returns true if the URL is within Drupal's base path.
-   *
-   * @param {string} url
-   *   The URL string to be tested.
-   *
-   * @return {bool}
-   *   `true` if local.
-   *
-   * @see https://github.com/jquery/jquery-ui/blob/1.11.4/ui/tabs.js#L58
-   */
   Drupal.url.isLocal = function (url) {
-    // Always use browser-derived absolute URLs in the comparison, to avoid
-    // attempts to break out of the base path using directory traversal.
     var absoluteUrl = Drupal.url.toAbsolute(url);
     var protocol = location.protocol;
 
-    // Consider URLs that match this site's base URL but use HTTPS instead of HTTP
-    // as local as well.
     if (protocol === 'http:' && absoluteUrl.indexOf('https:') === 0) {
       protocol = 'https:';
     }
     var baseUrl = protocol + '//' + location.host + drupalSettings.path.baseUrl.slice(0, -1);
 
-    // Decoding non-UTF-8 strings may throw an exception.
     try {
       absoluteUrl = decodeURIComponent(absoluteUrl);
-    }
-    catch (e) {
-      // Empty.
-    }
+    } catch (e) {}
     try {
       baseUrl = decodeURIComponent(baseUrl);
-    }
-    catch (e) {
-      // Empty.
-    }
+    } catch (e) {}
 
-    // The given URL matches the site's base URL, or has a path under the site's
-    // base URL.
     return absoluteUrl === baseUrl || absoluteUrl.indexOf(baseUrl + '/') === 0;
   };
 
-  /**
-   * Formats a string containing a count of items.
-   *
-   * This function ensures that the string is pluralized correctly. Since
-   * {@link Drupal.t} is called by this function, make sure not to pass
-   * already-localized strings to it.
-   *
-   * See the documentation of the server-side
-   * \Drupal\Core\StringTranslation\TranslationInterface::formatPlural()
-   * function for more details.
-   *
-   * @param {number} count
-   *   The item count to display.
-   * @param {string} singular
-   *   The string for the singular case. Please make sure it is clear this is
-   *   singular, to ease translation (e.g. use "1 new comment" instead of "1
-   *   new"). Do not use @count in the singular string.
-   * @param {string} plural
-   *   The string for the plural case. Please make sure it is clear this is
-   *   plural, to ease translation. Use @count in place of the item count, as in
-   *   "@count new comments".
-   * @param {object} [args]
-   *   An object of replacements pairs to make after translation. Incidences
-   *   of any key in this array are replaced with the corresponding value.
-   *   See {@link Drupal.formatString}.
-   *   Note that you do not need to include @count in this array.
-   *   This replacement is done automatically for the plural case.
-   * @param {object} [options]
-   *   The options to pass to the {@link Drupal.t} function.
-   *
-   * @return {string}
-   *   A translated string.
-   */
   Drupal.formatPlural = function (count, singular, plural, args, options) {
     args = args || {};
     args['@count'] = count;
@@ -510,56 +171,19 @@ window.Drupal = {behaviors: {}, locale: {}};
     var translations = Drupal.t(singular + pluralDelimiter + plural, args, options).split(pluralDelimiter);
     var index = 0;
 
-    // Determine the index of the plural form.
     if (typeof drupalTranslations !== 'undefined' && drupalTranslations.pluralFormula) {
       index = count in drupalTranslations.pluralFormula ? drupalTranslations.pluralFormula[count] : drupalTranslations.pluralFormula['default'];
-    }
-    else if (args['@count'] !== 1) {
+    } else if (args['@count'] !== 1) {
       index = 1;
     }
 
     return translations[index];
   };
 
-  /**
-   * Encodes a Drupal path for use in a URL.
-   *
-   * For aesthetic reasons slashes are not escaped.
-   *
-   * @param {string} item
-   *   Unencoded path.
-   *
-   * @return {string}
-   *   The encoded path.
-   */
   Drupal.encodePath = function (item) {
     return window.encodeURIComponent(item).replace(/%2F/g, '/');
   };
 
-  /**
-   * Generates the themed representation of a Drupal object.
-   *
-   * All requests for themed output must go through this function. It examines
-   * the request and routes it to the appropriate theme function. If the current
-   * theme does not provide an override function, the generic theme function is
-   * called.
-   *
-   * @example
-   * <caption>To retrieve the HTML for text that should be emphasized and
-   * displayed as a placeholder inside a sentence.</caption>
-   * Drupal.theme('placeholder', text);
-   *
-   * @namespace
-   *
-   * @param {function} func
-   *   The name of the theme function to call.
-   * @param {...args}
-   *   Additional arguments to pass along to the theme function.
-   *
-   * @return {string|object|HTMLElement|jQuery}
-   *   Any data the theme function returns. This could be a plain HTML string,
-   *   but also a complex object.
-   */
   Drupal.theme = function (func) {
     var args = Array.prototype.slice.apply(arguments, [1]);
     if (func in Drupal.theme) {
@@ -567,17 +191,7 @@ window.Drupal = {behaviors: {}, locale: {}};
     }
   };
 
-  /**
-   * Formats text for emphasized display in a placeholder inside a sentence.
-   *
-   * @param {string} str
-   *   The text to format (plain-text).
-   *
-   * @return {string}
-   *   The formatted text (html).
-   */
   Drupal.theme.placeholder = function (str) {
     return '<em class="placeholder">' + Drupal.checkPlain(str) + '</em>';
   };
-
-})(Drupal, window.drupalSettings, window.drupalTranslations);
+})(Drupal, window.drupalSettings, window.drupalTranslations);
\ No newline at end of file
diff --git a/core/misc/drupalSettingsLoader.es6.js b/core/misc/drupalSettingsLoader.es6.js
new file mode 100644
index 000000000000..7ff292efbfc5
--- /dev/null
+++ b/core/misc/drupalSettingsLoader.es6.js
@@ -0,0 +1,25 @@
+/**
+ * @file
+ * Parse inline JSON and initialize the drupalSettings global object.
+ */
+
+(function () {
+
+  'use strict';
+
+  // Use direct child elements to harden against XSS exploits when CSP is on.
+  var settingsElement = document.querySelector('head > script[type="application/json"][data-drupal-selector="drupal-settings-json"], body > script[type="application/json"][data-drupal-selector="drupal-settings-json"]');
+
+  /**
+   * Variable generated by Drupal with all the configuration created from PHP.
+   *
+   * @global
+   *
+   * @type {object}
+   */
+  window.drupalSettings = {};
+
+  if (settingsElement !== null) {
+    window.drupalSettings = JSON.parse(settingsElement.textContent);
+  }
+})();
diff --git a/core/misc/drupalSettingsLoader.js b/core/misc/drupalSettingsLoader.js
index 7ff292efbfc5..dbc2d2b20c36 100644
--- a/core/misc/drupalSettingsLoader.js
+++ b/core/misc/drupalSettingsLoader.js
@@ -1,25 +1,20 @@
 /**
- * @file
- * Parse inline JSON and initialize the drupalSettings global object.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./misc/drupalSettingsLoader.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function () {
 
   'use strict';
 
-  // Use direct child elements to harden against XSS exploits when CSP is on.
   var settingsElement = document.querySelector('head > script[type="application/json"][data-drupal-selector="drupal-settings-json"], body > script[type="application/json"][data-drupal-selector="drupal-settings-json"]');
 
-  /**
-   * Variable generated by Drupal with all the configuration created from PHP.
-   *
-   * @global
-   *
-   * @type {object}
-   */
   window.drupalSettings = {};
 
   if (settingsElement !== null) {
     window.drupalSettings = JSON.parse(settingsElement.textContent);
   }
-})();
+})();
\ No newline at end of file
diff --git a/core/misc/entity-form.es6.js b/core/misc/entity-form.es6.js
new file mode 100644
index 000000000000..87253c90223e
--- /dev/null
+++ b/core/misc/entity-form.es6.js
@@ -0,0 +1,57 @@
+/**
+ * @file
+ * Defines Javascript behaviors for the block_content module.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * Sets summaries about revision and translation of entities.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches summary behaviour entity form tabs.
+   *
+   *   Specifically, it updates summaries to the revision information and the
+   *   translation options.
+   */
+  Drupal.behaviors.entityContentDetailsSummaries = {
+    attach: function (context) {
+      var $context = $(context);
+      $context.find('.entity-content-form-revision-information').drupalSetSummary(function (context) {
+        var $revisionContext = $(context);
+        var revisionCheckbox = $revisionContext.find('.js-form-item-revision input');
+
+        // Return 'New revision' if the 'Create new revision' checkbox is checked,
+        // or if the checkbox doesn't exist, but the revision log does. For users
+        // without the "Administer content" permission the checkbox won't appear,
+        // but the revision log will if the content type is set to auto-revision.
+        if (revisionCheckbox.is(':checked') || (!revisionCheckbox.length && $revisionContext.find('.js-form-item-revision-log textarea').length)) {
+          return Drupal.t('New revision');
+        }
+
+        return Drupal.t('No revision');
+      });
+
+      $context.find('details.entity-translation-options').drupalSetSummary(function (context) {
+        var $translationContext = $(context);
+        var translate;
+        var $checkbox = $translationContext.find('.js-form-item-translation-translate input');
+
+        if ($checkbox.length) {
+          translate = $checkbox.is(':checked') ? Drupal.t('Needs to be updated') : Drupal.t('Does not need to be updated');
+        }
+        else {
+          $checkbox = $translationContext.find('.js-form-item-translation-retranslate input');
+          translate = $checkbox.is(':checked') ? Drupal.t('Flag other translations as outdated') : Drupal.t('Do not flag other translations as outdated');
+        }
+
+        return translate;
+      });
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/core/misc/entity-form.js b/core/misc/entity-form.js
index 87253c90223e..c00f84f4e4f7 100644
--- a/core/misc/entity-form.js
+++ b/core/misc/entity-form.js
@@ -1,35 +1,23 @@
 /**
- * @file
- * Defines Javascript behaviors for the block_content module.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./misc/entity-form.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   * Sets summaries about revision and translation of entities.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches summary behaviour entity form tabs.
-   *
-   *   Specifically, it updates summaries to the revision information and the
-   *   translation options.
-   */
   Drupal.behaviors.entityContentDetailsSummaries = {
-    attach: function (context) {
+    attach: function attach(context) {
       var $context = $(context);
       $context.find('.entity-content-form-revision-information').drupalSetSummary(function (context) {
         var $revisionContext = $(context);
         var revisionCheckbox = $revisionContext.find('.js-form-item-revision input');
 
-        // Return 'New revision' if the 'Create new revision' checkbox is checked,
-        // or if the checkbox doesn't exist, but the revision log does. For users
-        // without the "Administer content" permission the checkbox won't appear,
-        // but the revision log will if the content type is set to auto-revision.
-        if (revisionCheckbox.is(':checked') || (!revisionCheckbox.length && $revisionContext.find('.js-form-item-revision-log textarea').length)) {
+        if (revisionCheckbox.is(':checked') || !revisionCheckbox.length && $revisionContext.find('.js-form-item-revision-log textarea').length) {
           return Drupal.t('New revision');
         }
 
@@ -43,8 +31,7 @@
 
         if ($checkbox.length) {
           translate = $checkbox.is(':checked') ? Drupal.t('Needs to be updated') : Drupal.t('Does not need to be updated');
-        }
-        else {
+        } else {
           $checkbox = $translationContext.find('.js-form-item-translation-retranslate input');
           translate = $checkbox.is(':checked') ? Drupal.t('Flag other translations as outdated') : Drupal.t('Do not flag other translations as outdated');
         }
@@ -53,5 +40,4 @@
       });
     }
   };
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/misc/form.es6.js b/core/misc/form.es6.js
new file mode 100644
index 000000000000..7ca64fc4257c
--- /dev/null
+++ b/core/misc/form.es6.js
@@ -0,0 +1,250 @@
+/**
+ * @file
+ * Form features.
+ */
+
+/**
+ * Triggers when a value in the form changed.
+ *
+ * The event triggers when content is typed or pasted in a text field, before
+ * the change event triggers.
+ *
+ * @event formUpdated
+ */
+
+(function ($, Drupal, debounce) {
+
+  'use strict';
+
+  /**
+   * Retrieves the summary for the first element.
+   *
+   * @return {string}
+   *   The text of the summary.
+   */
+  $.fn.drupalGetSummary = function () {
+    var callback = this.data('summaryCallback');
+    return (this[0] && callback) ? $.trim(callback(this[0])) : '';
+  };
+
+  /**
+   * Sets the summary for all matched elements.
+   *
+   * @param {function} callback
+   *   Either a function that will be called each time the summary is
+   *   retrieved or a string (which is returned each time).
+   *
+   * @return {jQuery}
+   *   jQuery collection of the current element.
+   *
+   * @fires event:summaryUpdated
+   *
+   * @listens event:formUpdated
+   */
+  $.fn.drupalSetSummary = function (callback) {
+    var self = this;
+
+    // To facilitate things, the callback should always be a function. If it's
+    // not, we wrap it into an anonymous function which just returns the value.
+    if (typeof callback !== 'function') {
+      var val = callback;
+      callback = function () { return val; };
+    }
+
+    return this
+      .data('summaryCallback', callback)
+      // To prevent duplicate events, the handlers are first removed and then
+      // (re-)added.
+      .off('formUpdated.summary')
+      .on('formUpdated.summary', function () {
+        self.trigger('summaryUpdated');
+      })
+      // The actual summaryUpdated handler doesn't fire when the callback is
+      // changed, so we have to do this manually.
+      .trigger('summaryUpdated');
+  };
+
+  /**
+   * Prevents consecutive form submissions of identical form values.
+   *
+   * Repetitive form submissions that would submit the identical form values
+   * are prevented, unless the form values are different to the previously
+   * submitted values.
+   *
+   * This is a simplified re-implementation of a user-agent behavior that
+   * should be natively supported by major web browsers, but at this time, only
+   * Firefox has a built-in protection.
+   *
+   * A form value-based approach ensures that the constraint is triggered for
+   * consecutive, identical form submissions only. Compared to that, a form
+   * button-based approach would (1) rely on [visible] buttons to exist where
+   * technically not required and (2) require more complex state management if
+   * there are multiple buttons in a form.
+   *
+   * This implementation is based on form-level submit events only and relies
+   * on jQuery's serialize() method to determine submitted form values. As such,
+   * the following limitations exist:
+   *
+   * - Event handlers on form buttons that preventDefault() do not receive a
+   *   double-submit protection. That is deemed to be fine, since such button
+   *   events typically trigger reversible client-side or server-side
+   *   operations that are local to the context of a form only.
+   * - Changed values in advanced form controls, such as file inputs, are not
+   *   part of the form values being compared between consecutive form submits
+   *   (due to limitations of jQuery.serialize()). That is deemed to be
+   *   acceptable, because if the user forgot to attach a file, then the size of
+   *   HTTP payload will most likely be small enough to be fully passed to the
+   *   server endpoint within (milli)seconds. If a user mistakenly attached a
+   *   wrong file and is technically versed enough to cancel the form submission
+   *   (and HTTP payload) in order to attach a different file, then that
+   *   edge-case is not supported here.
+   *
+   * Lastly, all forms submitted via HTTP GET are idempotent by definition of
+   * HTTP standards, so excluded in this implementation.
+   *
+   * @type {Drupal~behavior}
+   */
+  Drupal.behaviors.formSingleSubmit = {
+    attach: function () {
+      function onFormSubmit(e) {
+        var $form = $(e.currentTarget);
+        var formValues = $form.serialize();
+        var previousValues = $form.attr('data-drupal-form-submit-last');
+        if (previousValues === formValues) {
+          e.preventDefault();
+        }
+        else {
+          $form.attr('data-drupal-form-submit-last', formValues);
+        }
+      }
+
+      $('body').once('form-single-submit')
+        .on('submit.singleSubmit', 'form:not([method~="GET"])', onFormSubmit);
+    }
+  };
+
+  /**
+   * Sends a 'formUpdated' event each time a form element is modified.
+   *
+   * @param {HTMLElement} element
+   *   The element to trigger a form updated event on.
+   *
+   * @fires event:formUpdated
+   */
+  function triggerFormUpdated(element) {
+    $(element).trigger('formUpdated');
+  }
+
+  /**
+   * Collects the IDs of all form fields in the given form.
+   *
+   * @param {HTMLFormElement} form
+   *   The form element to search.
+   *
+   * @return {Array}
+   *   Array of IDs for form fields.
+   */
+  function fieldsList(form) {
+    var $fieldList = $(form).find('[name]').map(function (index, element) {
+      // We use id to avoid name duplicates on radio fields and filter out
+      // elements with a name but no id.
+      return element.getAttribute('id');
+    });
+    // Return a true array.
+    return $.makeArray($fieldList);
+  }
+
+  /**
+   * Triggers the 'formUpdated' event on form elements when they are modified.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches formUpdated behaviors.
+   * @prop {Drupal~behaviorDetach} detach
+   *   Detaches formUpdated behaviors.
+   *
+   * @fires event:formUpdated
+   */
+  Drupal.behaviors.formUpdated = {
+    attach: function (context) {
+      var $context = $(context);
+      var contextIsForm = $context.is('form');
+      var $forms = (contextIsForm ? $context : $context.find('form')).once('form-updated');
+      var formFields;
+
+      if ($forms.length) {
+        // Initialize form behaviors, use $.makeArray to be able to use native
+        // forEach array method and have the callback parameters in the right
+        // order.
+        $.makeArray($forms).forEach(function (form) {
+          var events = 'change.formUpdated input.formUpdated ';
+          var eventHandler = debounce(function (event) { triggerFormUpdated(event.target); }, 300);
+          formFields = fieldsList(form).join(',');
+
+          form.setAttribute('data-drupal-form-fields', formFields);
+          $(form).on(events, eventHandler);
+        });
+      }
+      // On ajax requests context is the form element.
+      if (contextIsForm) {
+        formFields = fieldsList(context).join(',');
+        // @todo replace with form.getAttribute() when #1979468 is in.
+        var currentFields = $(context).attr('data-drupal-form-fields');
+        // If there has been a change in the fields or their order, trigger
+        // formUpdated.
+        if (formFields !== currentFields) {
+          triggerFormUpdated(context);
+        }
+      }
+
+    },
+    detach: function (context, settings, trigger) {
+      var $context = $(context);
+      var contextIsForm = $context.is('form');
+      if (trigger === 'unload') {
+        var $forms = (contextIsForm ? $context : $context.find('form')).removeOnce('form-updated');
+        if ($forms.length) {
+          $.makeArray($forms).forEach(function (form) {
+            form.removeAttribute('data-drupal-form-fields');
+            $(form).off('.formUpdated');
+          });
+        }
+      }
+    }
+  };
+
+  /**
+   * Prepopulate form fields with information from the visitor browser.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches the behavior for filling user info from browser.
+   */
+  Drupal.behaviors.fillUserInfoFromBrowser = {
+    attach: function (context, settings) {
+      var userInfo = ['name', 'mail', 'homepage'];
+      var $forms = $('[data-user-info-from-browser]').once('user-info-from-browser');
+      if ($forms.length) {
+        userInfo.map(function (info) {
+          var $element = $forms.find('[name=' + info + ']');
+          var browserData = localStorage.getItem('Drupal.visitor.' + info);
+          var emptyOrDefault = ($element.val() === '' || ($element.attr('data-drupal-default-value') === $element.val()));
+          if ($element.length && emptyOrDefault && browserData) {
+            $element.val(browserData);
+          }
+        });
+      }
+      $forms.on('submit', function () {
+        userInfo.map(function (info) {
+          var $element = $forms.find('[name=' + info + ']');
+          if ($element.length) {
+            localStorage.setItem('Drupal.visitor.' + info, $element.val());
+          }
+        });
+      });
+    }
+  };
+
+})(jQuery, Drupal, Drupal.debounce);
diff --git a/core/misc/form.js b/core/misc/form.js
index 7ca64fc4257c..5c3625cc2568 100644
--- a/core/misc/form.js
+++ b/core/misc/form.js
@@ -1,205 +1,95 @@
 /**
- * @file
- * Form features.
- */
-
-/**
- * Triggers when a value in the form changed.
- *
- * The event triggers when content is typed or pasted in a text field, before
- * the change event triggers.
- *
- * @event formUpdated
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./misc/form.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, debounce) {
 
   'use strict';
 
-  /**
-   * Retrieves the summary for the first element.
-   *
-   * @return {string}
-   *   The text of the summary.
-   */
   $.fn.drupalGetSummary = function () {
     var callback = this.data('summaryCallback');
-    return (this[0] && callback) ? $.trim(callback(this[0])) : '';
+    return this[0] && callback ? $.trim(callback(this[0])) : '';
   };
 
-  /**
-   * Sets the summary for all matched elements.
-   *
-   * @param {function} callback
-   *   Either a function that will be called each time the summary is
-   *   retrieved or a string (which is returned each time).
-   *
-   * @return {jQuery}
-   *   jQuery collection of the current element.
-   *
-   * @fires event:summaryUpdated
-   *
-   * @listens event:formUpdated
-   */
   $.fn.drupalSetSummary = function (callback) {
     var self = this;
 
-    // To facilitate things, the callback should always be a function. If it's
-    // not, we wrap it into an anonymous function which just returns the value.
     if (typeof callback !== 'function') {
       var val = callback;
-      callback = function () { return val; };
+      callback = function callback() {
+        return val;
+      };
     }
 
-    return this
-      .data('summaryCallback', callback)
-      // To prevent duplicate events, the handlers are first removed and then
-      // (re-)added.
-      .off('formUpdated.summary')
-      .on('formUpdated.summary', function () {
-        self.trigger('summaryUpdated');
-      })
-      // The actual summaryUpdated handler doesn't fire when the callback is
-      // changed, so we have to do this manually.
-      .trigger('summaryUpdated');
+    return this.data('summaryCallback', callback).off('formUpdated.summary').on('formUpdated.summary', function () {
+      self.trigger('summaryUpdated');
+    }).trigger('summaryUpdated');
   };
 
-  /**
-   * Prevents consecutive form submissions of identical form values.
-   *
-   * Repetitive form submissions that would submit the identical form values
-   * are prevented, unless the form values are different to the previously
-   * submitted values.
-   *
-   * This is a simplified re-implementation of a user-agent behavior that
-   * should be natively supported by major web browsers, but at this time, only
-   * Firefox has a built-in protection.
-   *
-   * A form value-based approach ensures that the constraint is triggered for
-   * consecutive, identical form submissions only. Compared to that, a form
-   * button-based approach would (1) rely on [visible] buttons to exist where
-   * technically not required and (2) require more complex state management if
-   * there are multiple buttons in a form.
-   *
-   * This implementation is based on form-level submit events only and relies
-   * on jQuery's serialize() method to determine submitted form values. As such,
-   * the following limitations exist:
-   *
-   * - Event handlers on form buttons that preventDefault() do not receive a
-   *   double-submit protection. That is deemed to be fine, since such button
-   *   events typically trigger reversible client-side or server-side
-   *   operations that are local to the context of a form only.
-   * - Changed values in advanced form controls, such as file inputs, are not
-   *   part of the form values being compared between consecutive form submits
-   *   (due to limitations of jQuery.serialize()). That is deemed to be
-   *   acceptable, because if the user forgot to attach a file, then the size of
-   *   HTTP payload will most likely be small enough to be fully passed to the
-   *   server endpoint within (milli)seconds. If a user mistakenly attached a
-   *   wrong file and is technically versed enough to cancel the form submission
-   *   (and HTTP payload) in order to attach a different file, then that
-   *   edge-case is not supported here.
-   *
-   * Lastly, all forms submitted via HTTP GET are idempotent by definition of
-   * HTTP standards, so excluded in this implementation.
-   *
-   * @type {Drupal~behavior}
-   */
   Drupal.behaviors.formSingleSubmit = {
-    attach: function () {
+    attach: function attach() {
       function onFormSubmit(e) {
         var $form = $(e.currentTarget);
         var formValues = $form.serialize();
         var previousValues = $form.attr('data-drupal-form-submit-last');
         if (previousValues === formValues) {
           e.preventDefault();
-        }
-        else {
+        } else {
           $form.attr('data-drupal-form-submit-last', formValues);
         }
       }
 
-      $('body').once('form-single-submit')
-        .on('submit.singleSubmit', 'form:not([method~="GET"])', onFormSubmit);
+      $('body').once('form-single-submit').on('submit.singleSubmit', 'form:not([method~="GET"])', onFormSubmit);
     }
   };
 
-  /**
-   * Sends a 'formUpdated' event each time a form element is modified.
-   *
-   * @param {HTMLElement} element
-   *   The element to trigger a form updated event on.
-   *
-   * @fires event:formUpdated
-   */
   function triggerFormUpdated(element) {
     $(element).trigger('formUpdated');
   }
 
-  /**
-   * Collects the IDs of all form fields in the given form.
-   *
-   * @param {HTMLFormElement} form
-   *   The form element to search.
-   *
-   * @return {Array}
-   *   Array of IDs for form fields.
-   */
   function fieldsList(form) {
     var $fieldList = $(form).find('[name]').map(function (index, element) {
-      // We use id to avoid name duplicates on radio fields and filter out
-      // elements with a name but no id.
       return element.getAttribute('id');
     });
-    // Return a true array.
+
     return $.makeArray($fieldList);
   }
 
-  /**
-   * Triggers the 'formUpdated' event on form elements when they are modified.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches formUpdated behaviors.
-   * @prop {Drupal~behaviorDetach} detach
-   *   Detaches formUpdated behaviors.
-   *
-   * @fires event:formUpdated
-   */
   Drupal.behaviors.formUpdated = {
-    attach: function (context) {
+    attach: function attach(context) {
       var $context = $(context);
       var contextIsForm = $context.is('form');
       var $forms = (contextIsForm ? $context : $context.find('form')).once('form-updated');
       var formFields;
 
       if ($forms.length) {
-        // Initialize form behaviors, use $.makeArray to be able to use native
-        // forEach array method and have the callback parameters in the right
-        // order.
         $.makeArray($forms).forEach(function (form) {
           var events = 'change.formUpdated input.formUpdated ';
-          var eventHandler = debounce(function (event) { triggerFormUpdated(event.target); }, 300);
+          var eventHandler = debounce(function (event) {
+            triggerFormUpdated(event.target);
+          }, 300);
           formFields = fieldsList(form).join(',');
 
           form.setAttribute('data-drupal-form-fields', formFields);
           $(form).on(events, eventHandler);
         });
       }
-      // On ajax requests context is the form element.
+
       if (contextIsForm) {
         formFields = fieldsList(context).join(',');
-        // @todo replace with form.getAttribute() when #1979468 is in.
+
         var currentFields = $(context).attr('data-drupal-form-fields');
-        // If there has been a change in the fields or their order, trigger
-        // formUpdated.
+
         if (formFields !== currentFields) {
           triggerFormUpdated(context);
         }
       }
-
     },
-    detach: function (context, settings, trigger) {
+    detach: function detach(context, settings, trigger) {
       var $context = $(context);
       var contextIsForm = $context.is('form');
       if (trigger === 'unload') {
@@ -214,23 +104,15 @@
     }
   };
 
-  /**
-   * Prepopulate form fields with information from the visitor browser.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches the behavior for filling user info from browser.
-   */
   Drupal.behaviors.fillUserInfoFromBrowser = {
-    attach: function (context, settings) {
+    attach: function attach(context, settings) {
       var userInfo = ['name', 'mail', 'homepage'];
       var $forms = $('[data-user-info-from-browser]').once('user-info-from-browser');
       if ($forms.length) {
         userInfo.map(function (info) {
           var $element = $forms.find('[name=' + info + ']');
           var browserData = localStorage.getItem('Drupal.visitor.' + info);
-          var emptyOrDefault = ($element.val() === '' || ($element.attr('data-drupal-default-value') === $element.val()));
+          var emptyOrDefault = $element.val() === '' || $element.attr('data-drupal-default-value') === $element.val();
           if ($element.length && emptyOrDefault && browserData) {
             $element.val(browserData);
           }
@@ -246,5 +128,4 @@
       });
     }
   };
-
-})(jQuery, Drupal, Drupal.debounce);
+})(jQuery, Drupal, Drupal.debounce);
\ No newline at end of file
diff --git a/core/misc/machine-name.es6.js b/core/misc/machine-name.es6.js
new file mode 100644
index 000000000000..e76292e265cc
--- /dev/null
+++ b/core/misc/machine-name.es6.js
@@ -0,0 +1,211 @@
+/**
+ * @file
+ * Machine name functionality.
+ */
+
+(function ($, Drupal, drupalSettings) {
+
+  'use strict';
+
+  /**
+   * Attach the machine-readable name form element behavior.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches machine-name behaviors.
+   */
+  Drupal.behaviors.machineName = {
+
+    /**
+     * Attaches the behavior.
+     *
+     * @param {Element} context
+     *   The context for attaching the behavior.
+     * @param {object} settings
+     *   Settings object.
+     * @param {object} settings.machineName
+     *   A list of elements to process, keyed by the HTML ID of the form
+     *   element containing the human-readable value. Each element is an object
+     *   defining the following properties:
+     *   - target: The HTML ID of the machine name form element.
+     *   - suffix: The HTML ID of a container to show the machine name preview
+     *     in (usually a field suffix after the human-readable name
+     *     form element).
+     *   - label: The label to show for the machine name preview.
+     *   - replace_pattern: A regular expression (without modifiers) matching
+     *     disallowed characters in the machine name; e.g., '[^a-z0-9]+'.
+     *   - replace: A character to replace disallowed characters with; e.g.,
+     *     '_' or '-'.
+     *   - standalone: Whether the preview should stay in its own element
+     *     rather than the suffix of the source element.
+     *   - field_prefix: The #field_prefix of the form element.
+     *   - field_suffix: The #field_suffix of the form element.
+     */
+    attach: function (context, settings) {
+      var self = this;
+      var $context = $(context);
+      var timeout = null;
+      var xhr = null;
+
+      function clickEditHandler(e) {
+        var data = e.data;
+        data.$wrapper.removeClass('visually-hidden');
+        data.$target.trigger('focus');
+        data.$suffix.hide();
+        data.$source.off('.machineName');
+      }
+
+      function machineNameHandler(e) {
+        var data = e.data;
+        var options = data.options;
+        var baseValue = $(e.target).val();
+
+        var rx = new RegExp(options.replace_pattern, 'g');
+        var expected = baseValue.toLowerCase().replace(rx, options.replace).substr(0, options.maxlength);
+
+        // Abort the last pending request because the label has changed and it
+        // is no longer valid.
+        if (xhr && xhr.readystate !== 4) {
+          xhr.abort();
+          xhr = null;
+        }
+
+        // Wait 300 milliseconds for Ajax request since the last event to update
+        // the machine name i.e., after the user has stopped typing.
+        if (timeout) {
+          clearTimeout(timeout);
+          timeout = null;
+        }
+        if (baseValue.toLowerCase() !== expected) {
+          timeout = setTimeout(function () {
+            xhr = self.transliterate(baseValue, options).done(function (machine) {
+              self.showMachineName(machine.substr(0, options.maxlength), data);
+            });
+          }, 300);
+        }
+        else {
+          self.showMachineName(expected, data);
+        }
+      }
+
+      Object.keys(settings.machineName).forEach(function (source_id) {
+        var machine = '';
+        var eventData;
+        var options = settings.machineName[source_id];
+
+        var $source = $context.find(source_id).addClass('machine-name-source').once('machine-name');
+        var $target = $context.find(options.target).addClass('machine-name-target');
+        var $suffix = $context.find(options.suffix);
+        var $wrapper = $target.closest('.js-form-item');
+        // All elements have to exist.
+        if (!$source.length || !$target.length || !$suffix.length || !$wrapper.length) {
+          return;
+        }
+        // Skip processing upon a form validation error on the machine name.
+        if ($target.hasClass('error')) {
+          return;
+        }
+        // Figure out the maximum length for the machine name.
+        options.maxlength = $target.attr('maxlength');
+        // Hide the form item container of the machine name form element.
+        $wrapper.addClass('visually-hidden');
+        // Determine the initial machine name value. Unless the machine name
+        // form element is disabled or not empty, the initial default value is
+        // based on the human-readable form element value.
+        if ($target.is(':disabled') || $target.val() !== '') {
+          machine = $target.val();
+        }
+        else if ($source.val() !== '') {
+          machine = self.transliterate($source.val(), options);
+        }
+        // Append the machine name preview to the source field.
+        var $preview = $('<span class="machine-name-value">' + options.field_prefix + Drupal.checkPlain(machine) + options.field_suffix + '</span>');
+        $suffix.empty();
+        if (options.label) {
+          $suffix.append('<span class="machine-name-label">' + options.label + ': </span>');
+        }
+        $suffix.append($preview);
+
+        // If the machine name cannot be edited, stop further processing.
+        if ($target.is(':disabled')) {
+          return;
+        }
+
+        eventData = {
+          $source: $source,
+          $target: $target,
+          $suffix: $suffix,
+          $wrapper: $wrapper,
+          $preview: $preview,
+          options: options
+        };
+        // If it is editable, append an edit link.
+        var $link = $('<span class="admin-link"><button type="button" class="link">' + Drupal.t('Edit') + '</button></span>').on('click', eventData, clickEditHandler);
+        $suffix.append($link);
+
+        // Preview the machine name in realtime when the human-readable name
+        // changes, but only if there is no machine name yet; i.e., only upon
+        // initial creation, not when editing.
+        if ($target.val() === '') {
+          $source.on('formUpdated.machineName', eventData, machineNameHandler)
+            // Initialize machine name preview.
+            .trigger('formUpdated.machineName');
+        }
+
+        // Add a listener for an invalid event on the machine name input
+        // to show its container and focus it.
+        $target.on('invalid', eventData, clickEditHandler);
+      });
+    },
+
+    showMachineName: function (machine, data) {
+      var settings = data.options;
+      // Set the machine name to the transliterated value.
+      if (machine !== '') {
+        if (machine !== settings.replace) {
+          data.$target.val(machine);
+          data.$preview.html(settings.field_prefix + Drupal.checkPlain(machine) + settings.field_suffix);
+        }
+        data.$suffix.show();
+      }
+      else {
+        data.$suffix.hide();
+        data.$target.val(machine);
+        data.$preview.empty();
+      }
+    },
+
+    /**
+     * Transliterate a human-readable name to a machine name.
+     *
+     * @param {string} source
+     *   A string to transliterate.
+     * @param {object} settings
+     *   The machine name settings for the corresponding field.
+     * @param {string} settings.replace_pattern
+     *   A regular expression (without modifiers) matching disallowed characters
+     *   in the machine name; e.g., '[^a-z0-9]+'.
+     * @param {string} settings.replace_token
+     *   A token to validate the regular expression.
+     * @param {string} settings.replace
+     *   A character to replace disallowed characters with; e.g., '_' or '-'.
+     * @param {number} settings.maxlength
+     *   The maximum length of the machine name.
+     *
+     * @return {jQuery}
+     *   The transliterated source string.
+     */
+    transliterate: function (source, settings) {
+      return $.get(Drupal.url('machine_name/transliterate'), {
+        text: source,
+        langcode: drupalSettings.langcode,
+        replace_pattern: settings.replace_pattern,
+        replace_token: settings.replace_token,
+        replace: settings.replace,
+        lowercase: true
+      });
+    }
+  };
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/misc/machine-name.js b/core/misc/machine-name.js
index e76292e265cc..16fbdcb8c7c5 100644
--- a/core/misc/machine-name.js
+++ b/core/misc/machine-name.js
@@ -1,48 +1,17 @@
 /**
- * @file
- * Machine name functionality.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./misc/machine-name.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings) {
 
   'use strict';
 
-  /**
-   * Attach the machine-readable name form element behavior.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches machine-name behaviors.
-   */
   Drupal.behaviors.machineName = {
-
-    /**
-     * Attaches the behavior.
-     *
-     * @param {Element} context
-     *   The context for attaching the behavior.
-     * @param {object} settings
-     *   Settings object.
-     * @param {object} settings.machineName
-     *   A list of elements to process, keyed by the HTML ID of the form
-     *   element containing the human-readable value. Each element is an object
-     *   defining the following properties:
-     *   - target: The HTML ID of the machine name form element.
-     *   - suffix: The HTML ID of a container to show the machine name preview
-     *     in (usually a field suffix after the human-readable name
-     *     form element).
-     *   - label: The label to show for the machine name preview.
-     *   - replace_pattern: A regular expression (without modifiers) matching
-     *     disallowed characters in the machine name; e.g., '[^a-z0-9]+'.
-     *   - replace: A character to replace disallowed characters with; e.g.,
-     *     '_' or '-'.
-     *   - standalone: Whether the preview should stay in its own element
-     *     rather than the suffix of the source element.
-     *   - field_prefix: The #field_prefix of the form element.
-     *   - field_suffix: The #field_suffix of the form element.
-     */
-    attach: function (context, settings) {
+    attach: function attach(context, settings) {
       var self = this;
       var $context = $(context);
       var timeout = null;
@@ -64,15 +33,11 @@
         var rx = new RegExp(options.replace_pattern, 'g');
         var expected = baseValue.toLowerCase().replace(rx, options.replace).substr(0, options.maxlength);
 
-        // Abort the last pending request because the label has changed and it
-        // is no longer valid.
         if (xhr && xhr.readystate !== 4) {
           xhr.abort();
           xhr = null;
         }
 
-        // Wait 300 milliseconds for Ajax request since the last event to update
-        // the machine name i.e., after the user has stopped typing.
         if (timeout) {
           clearTimeout(timeout);
           timeout = null;
@@ -83,8 +48,7 @@
               self.showMachineName(machine.substr(0, options.maxlength), data);
             });
           }, 300);
-        }
-        else {
+        } else {
           self.showMachineName(expected, data);
         }
       }
@@ -98,28 +62,25 @@
         var $target = $context.find(options.target).addClass('machine-name-target');
         var $suffix = $context.find(options.suffix);
         var $wrapper = $target.closest('.js-form-item');
-        // All elements have to exist.
+
         if (!$source.length || !$target.length || !$suffix.length || !$wrapper.length) {
           return;
         }
-        // Skip processing upon a form validation error on the machine name.
+
         if ($target.hasClass('error')) {
           return;
         }
-        // Figure out the maximum length for the machine name.
+
         options.maxlength = $target.attr('maxlength');
-        // Hide the form item container of the machine name form element.
+
         $wrapper.addClass('visually-hidden');
-        // Determine the initial machine name value. Unless the machine name
-        // form element is disabled or not empty, the initial default value is
-        // based on the human-readable form element value.
+
         if ($target.is(':disabled') || $target.val() !== '') {
           machine = $target.val();
-        }
-        else if ($source.val() !== '') {
+        } else if ($source.val() !== '') {
           machine = self.transliterate($source.val(), options);
         }
-        // Append the machine name preview to the source field.
+
         var $preview = $('<span class="machine-name-value">' + options.field_prefix + Drupal.checkPlain(machine) + options.field_suffix + '</span>');
         $suffix.empty();
         if (options.label) {
@@ -127,7 +88,6 @@
         }
         $suffix.append($preview);
 
-        // If the machine name cannot be edited, stop further processing.
         if ($target.is(':disabled')) {
           return;
         }
@@ -140,63 +100,35 @@
           $preview: $preview,
           options: options
         };
-        // If it is editable, append an edit link.
+
         var $link = $('<span class="admin-link"><button type="button" class="link">' + Drupal.t('Edit') + '</button></span>').on('click', eventData, clickEditHandler);
         $suffix.append($link);
 
-        // Preview the machine name in realtime when the human-readable name
-        // changes, but only if there is no machine name yet; i.e., only upon
-        // initial creation, not when editing.
         if ($target.val() === '') {
-          $source.on('formUpdated.machineName', eventData, machineNameHandler)
-            // Initialize machine name preview.
-            .trigger('formUpdated.machineName');
+          $source.on('formUpdated.machineName', eventData, machineNameHandler).trigger('formUpdated.machineName');
         }
 
-        // Add a listener for an invalid event on the machine name input
-        // to show its container and focus it.
         $target.on('invalid', eventData, clickEditHandler);
       });
     },
 
-    showMachineName: function (machine, data) {
+    showMachineName: function showMachineName(machine, data) {
       var settings = data.options;
-      // Set the machine name to the transliterated value.
+
       if (machine !== '') {
         if (machine !== settings.replace) {
           data.$target.val(machine);
           data.$preview.html(settings.field_prefix + Drupal.checkPlain(machine) + settings.field_suffix);
         }
         data.$suffix.show();
-      }
-      else {
+      } else {
         data.$suffix.hide();
         data.$target.val(machine);
         data.$preview.empty();
       }
     },
 
-    /**
-     * Transliterate a human-readable name to a machine name.
-     *
-     * @param {string} source
-     *   A string to transliterate.
-     * @param {object} settings
-     *   The machine name settings for the corresponding field.
-     * @param {string} settings.replace_pattern
-     *   A regular expression (without modifiers) matching disallowed characters
-     *   in the machine name; e.g., '[^a-z0-9]+'.
-     * @param {string} settings.replace_token
-     *   A token to validate the regular expression.
-     * @param {string} settings.replace
-     *   A character to replace disallowed characters with; e.g., '_' or '-'.
-     * @param {number} settings.maxlength
-     *   The maximum length of the machine name.
-     *
-     * @return {jQuery}
-     *   The transliterated source string.
-     */
-    transliterate: function (source, settings) {
+    transliterate: function transliterate(source, settings) {
       return $.get(Drupal.url('machine_name/transliterate'), {
         text: source,
         langcode: drupalSettings.langcode,
@@ -207,5 +139,4 @@
       });
     }
   };
-
-})(jQuery, Drupal, drupalSettings);
+})(jQuery, Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/misc/progress.es6.js b/core/misc/progress.es6.js
new file mode 100644
index 000000000000..a6694892b270
--- /dev/null
+++ b/core/misc/progress.es6.js
@@ -0,0 +1,169 @@
+/**
+ * @file
+ * Progress bar.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * Theme function for the progress bar.
+   *
+   * @param {string} id
+   *   The id for the progress bar.
+   *
+   * @return {string}
+   *   The HTML for the progress bar.
+   */
+  Drupal.theme.progressBar = function (id) {
+    return '<div id="' + id + '" class="progress" aria-live="polite">' +
+      '<div class="progress__label">&nbsp;</div>' +
+      '<div class="progress__track"><div class="progress__bar"></div></div>' +
+      '<div class="progress__percentage"></div>' +
+      '<div class="progress__description">&nbsp;</div>' +
+      '</div>';
+  };
+
+  /**
+   * A progressbar object. Initialized with the given id. Must be inserted into
+   * the DOM afterwards through progressBar.element.
+   *
+   * Method is the function which will perform the HTTP request to get the
+   * progress bar state. Either "GET" or "POST".
+   *
+   * @example
+   * pb = new Drupal.ProgressBar('myProgressBar');
+   * some_element.appendChild(pb.element);
+   *
+   * @constructor
+   *
+   * @param {string} id
+   *   The id for the progressbar.
+   * @param {function} updateCallback
+   *   Callback to run on update.
+   * @param {string} method
+   *   HTTP method to use.
+   * @param {function} errorCallback
+   *   Callback to call on error.
+   */
+  Drupal.ProgressBar = function (id, updateCallback, method, errorCallback) {
+    this.id = id;
+    this.method = method || 'GET';
+    this.updateCallback = updateCallback;
+    this.errorCallback = errorCallback;
+
+    // The WAI-ARIA setting aria-live="polite" will announce changes after
+    // users
+    // have completed their current activity and not interrupt the screen
+    // reader.
+    this.element = $(Drupal.theme('progressBar', id));
+  };
+
+  $.extend(Drupal.ProgressBar.prototype, /** @lends Drupal.ProgressBar# */{
+
+    /**
+     * Set the percentage and status message for the progressbar.
+     *
+     * @param {number} percentage
+     *   The progress percentage.
+     * @param {string} message
+     *   The message to show the user.
+     * @param {string} label
+     *   The text for the progressbar label.
+     */
+    setProgress: function (percentage, message, label) {
+      if (percentage >= 0 && percentage <= 100) {
+        $(this.element).find('div.progress__bar').css('width', percentage + '%');
+        $(this.element).find('div.progress__percentage').html(percentage + '%');
+      }
+      $('div.progress__description', this.element).html(message);
+      $('div.progress__label', this.element).html(label);
+      if (this.updateCallback) {
+        this.updateCallback(percentage, message, this);
+      }
+    },
+
+    /**
+     * Start monitoring progress via Ajax.
+     *
+     * @param {string} uri
+     *   The URI to use for monitoring.
+     * @param {number} delay
+     *   The delay for calling the monitoring URI.
+     */
+    startMonitoring: function (uri, delay) {
+      this.delay = delay;
+      this.uri = uri;
+      this.sendPing();
+    },
+
+    /**
+     * Stop monitoring progress via Ajax.
+     */
+    stopMonitoring: function () {
+      clearTimeout(this.timer);
+      // This allows monitoring to be stopped from within the callback.
+      this.uri = null;
+    },
+
+    /**
+     * Request progress data from server.
+     */
+    sendPing: function () {
+      if (this.timer) {
+        clearTimeout(this.timer);
+      }
+      if (this.uri) {
+        var pb = this;
+        // When doing a post request, you need non-null data. Otherwise a
+        // HTTP 411 or HTTP 406 (with Apache mod_security) error may result.
+        var uri = this.uri;
+        if (uri.indexOf('?') === -1) {
+          uri += '?';
+        }
+        else {
+          uri += '&';
+        }
+        uri += '_format=json';
+        $.ajax({
+          type: this.method,
+          url: uri,
+          data: '',
+          dataType: 'json',
+          success: function (progress) {
+            // Display errors.
+            if (progress.status === 0) {
+              pb.displayError(progress.data);
+              return;
+            }
+            // Update display.
+            pb.setProgress(progress.percentage, progress.message, progress.label);
+            // Schedule next timer.
+            pb.timer = setTimeout(function () { pb.sendPing(); }, pb.delay);
+          },
+          error: function (xmlhttp) {
+            var e = new Drupal.AjaxError(xmlhttp, pb.uri);
+            pb.displayError('<pre>' + e.message + '</pre>');
+          }
+        });
+      }
+    },
+
+    /**
+     * Display errors on the page.
+     *
+     * @param {string} string
+     *   The error message to show the user.
+     */
+    displayError: function (string) {
+      var error = $('<div class="messages messages--error"></div>').html(string);
+      $(this.element).before(error).hide();
+
+      if (this.errorCallback) {
+        this.errorCallback(this);
+      }
+    }
+  });
+
+})(jQuery, Drupal);
diff --git a/core/misc/progress.js b/core/misc/progress.js
index a6694892b270..bcba9c396bf3 100644
--- a/core/misc/progress.js
+++ b/core/misc/progress.js
@@ -1,78 +1,30 @@
 /**
- * @file
- * Progress bar.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./misc/progress.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   * Theme function for the progress bar.
-   *
-   * @param {string} id
-   *   The id for the progress bar.
-   *
-   * @return {string}
-   *   The HTML for the progress bar.
-   */
   Drupal.theme.progressBar = function (id) {
-    return '<div id="' + id + '" class="progress" aria-live="polite">' +
-      '<div class="progress__label">&nbsp;</div>' +
-      '<div class="progress__track"><div class="progress__bar"></div></div>' +
-      '<div class="progress__percentage"></div>' +
-      '<div class="progress__description">&nbsp;</div>' +
-      '</div>';
+    return '<div id="' + id + '" class="progress" aria-live="polite">' + '<div class="progress__label">&nbsp;</div>' + '<div class="progress__track"><div class="progress__bar"></div></div>' + '<div class="progress__percentage"></div>' + '<div class="progress__description">&nbsp;</div>' + '</div>';
   };
 
-  /**
-   * A progressbar object. Initialized with the given id. Must be inserted into
-   * the DOM afterwards through progressBar.element.
-   *
-   * Method is the function which will perform the HTTP request to get the
-   * progress bar state. Either "GET" or "POST".
-   *
-   * @example
-   * pb = new Drupal.ProgressBar('myProgressBar');
-   * some_element.appendChild(pb.element);
-   *
-   * @constructor
-   *
-   * @param {string} id
-   *   The id for the progressbar.
-   * @param {function} updateCallback
-   *   Callback to run on update.
-   * @param {string} method
-   *   HTTP method to use.
-   * @param {function} errorCallback
-   *   Callback to call on error.
-   */
   Drupal.ProgressBar = function (id, updateCallback, method, errorCallback) {
     this.id = id;
     this.method = method || 'GET';
     this.updateCallback = updateCallback;
     this.errorCallback = errorCallback;
 
-    // The WAI-ARIA setting aria-live="polite" will announce changes after
-    // users
-    // have completed their current activity and not interrupt the screen
-    // reader.
     this.element = $(Drupal.theme('progressBar', id));
   };
 
-  $.extend(Drupal.ProgressBar.prototype, /** @lends Drupal.ProgressBar# */{
-
-    /**
-     * Set the percentage and status message for the progressbar.
-     *
-     * @param {number} percentage
-     *   The progress percentage.
-     * @param {string} message
-     *   The message to show the user.
-     * @param {string} label
-     *   The text for the progressbar label.
-     */
-    setProgress: function (percentage, message, label) {
+  $.extend(Drupal.ProgressBar.prototype, {
+    setProgress: function setProgress(percentage, message, label) {
       if (percentage >= 0 && percentage <= 100) {
         $(this.element).find('div.progress__bar').css('width', percentage + '%');
         $(this.element).find('div.progress__percentage').html(percentage + '%');
@@ -84,45 +36,29 @@
       }
     },
 
-    /**
-     * Start monitoring progress via Ajax.
-     *
-     * @param {string} uri
-     *   The URI to use for monitoring.
-     * @param {number} delay
-     *   The delay for calling the monitoring URI.
-     */
-    startMonitoring: function (uri, delay) {
+    startMonitoring: function startMonitoring(uri, delay) {
       this.delay = delay;
       this.uri = uri;
       this.sendPing();
     },
 
-    /**
-     * Stop monitoring progress via Ajax.
-     */
-    stopMonitoring: function () {
+    stopMonitoring: function stopMonitoring() {
       clearTimeout(this.timer);
-      // This allows monitoring to be stopped from within the callback.
+
       this.uri = null;
     },
 
-    /**
-     * Request progress data from server.
-     */
-    sendPing: function () {
+    sendPing: function sendPing() {
       if (this.timer) {
         clearTimeout(this.timer);
       }
       if (this.uri) {
         var pb = this;
-        // When doing a post request, you need non-null data. Otherwise a
-        // HTTP 411 or HTTP 406 (with Apache mod_security) error may result.
+
         var uri = this.uri;
         if (uri.indexOf('?') === -1) {
           uri += '?';
-        }
-        else {
+        } else {
           uri += '&';
         }
         uri += '_format=json';
@@ -131,18 +67,19 @@
           url: uri,
           data: '',
           dataType: 'json',
-          success: function (progress) {
-            // Display errors.
+          success: function success(progress) {
             if (progress.status === 0) {
               pb.displayError(progress.data);
               return;
             }
-            // Update display.
+
             pb.setProgress(progress.percentage, progress.message, progress.label);
-            // Schedule next timer.
-            pb.timer = setTimeout(function () { pb.sendPing(); }, pb.delay);
+
+            pb.timer = setTimeout(function () {
+              pb.sendPing();
+            }, pb.delay);
           },
-          error: function (xmlhttp) {
+          error: function error(xmlhttp) {
             var e = new Drupal.AjaxError(xmlhttp, pb.uri);
             pb.displayError('<pre>' + e.message + '</pre>');
           }
@@ -150,13 +87,7 @@
       }
     },
 
-    /**
-     * Display errors on the page.
-     *
-     * @param {string} string
-     *   The error message to show the user.
-     */
-    displayError: function (string) {
+    displayError: function displayError(string) {
       var error = $('<div class="messages messages--error"></div>').html(string);
       $(this.element).before(error).hide();
 
@@ -165,5 +96,4 @@
       }
     }
   });
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/misc/states.es6.js b/core/misc/states.es6.js
new file mode 100644
index 000000000000..24374b625f7a
--- /dev/null
+++ b/core/misc/states.es6.js
@@ -0,0 +1,724 @@
+/**
+ * @file
+ * Drupal's states library.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * The base States namespace.
+   *
+   * Having the local states variable allows us to use the States namespace
+   * without having to always declare "Drupal.states".
+   *
+   * @namespace Drupal.states
+   */
+  var states = Drupal.states = {
+
+    /**
+     * An array of functions that should be postponed.
+     */
+    postponed: []
+  };
+
+  /**
+   * Attaches the states.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches states behaviors.
+   */
+  Drupal.behaviors.states = {
+    attach: function (context, settings) {
+      var $states = $(context).find('[data-drupal-states]');
+      var config;
+      var state;
+      var il = $states.length;
+      for (var i = 0; i < il; i++) {
+        config = JSON.parse($states[i].getAttribute('data-drupal-states'));
+        for (state in config) {
+          if (config.hasOwnProperty(state)) {
+            new states.Dependent({
+              element: $($states[i]),
+              state: states.State.sanitize(state),
+              constraints: config[state]
+            });
+          }
+        }
+      }
+
+      // Execute all postponed functions now.
+      while (states.postponed.length) {
+        (states.postponed.shift())();
+      }
+    }
+  };
+
+  /**
+   * Object representing an element that depends on other elements.
+   *
+   * @constructor Drupal.states.Dependent
+   *
+   * @param {object} args
+   *   Object with the following keys (all of which are required)
+   * @param {jQuery} args.element
+   *   A jQuery object of the dependent element
+   * @param {Drupal.states.State} args.state
+   *   A State object describing the state that is dependent
+   * @param {object} args.constraints
+   *   An object with dependency specifications. Lists all elements that this
+   *   element depends on. It can be nested and can contain
+   *   arbitrary AND and OR clauses.
+   */
+  states.Dependent = function (args) {
+    $.extend(this, {values: {}, oldValue: null}, args);
+
+    this.dependees = this.getDependees();
+    for (var selector in this.dependees) {
+      if (this.dependees.hasOwnProperty(selector)) {
+        this.initializeDependee(selector, this.dependees[selector]);
+      }
+    }
+  };
+
+  /**
+   * Comparison functions for comparing the value of an element with the
+   * specification from the dependency settings. If the object type can't be
+   * found in this list, the === operator is used by default.
+   *
+   * @name Drupal.states.Dependent.comparisons
+   *
+   * @prop {function} RegExp
+   * @prop {function} Function
+   * @prop {function} Number
+   */
+  states.Dependent.comparisons = {
+    RegExp: function (reference, value) {
+      return reference.test(value);
+    },
+    Function: function (reference, value) {
+      // The "reference" variable is a comparison function.
+      return reference(value);
+    },
+    Number: function (reference, value) {
+      // If "reference" is a number and "value" is a string, then cast
+      // reference as a string before applying the strict comparison in
+      // compare().
+      // Otherwise numeric keys in the form's #states array fail to match
+      // string values returned from jQuery's val().
+      return (typeof value === 'string') ? compare(reference.toString(), value) : compare(reference, value);
+    }
+  };
+
+  states.Dependent.prototype = {
+
+    /**
+     * Initializes one of the elements this dependent depends on.
+     *
+     * @memberof Drupal.states.Dependent#
+     *
+     * @param {string} selector
+     *   The CSS selector describing the dependee.
+     * @param {object} dependeeStates
+     *   The list of states that have to be monitored for tracking the
+     *   dependee's compliance status.
+     */
+    initializeDependee: function (selector, dependeeStates) {
+      var state;
+      var self = this;
+
+      function stateEventHandler(e) {
+        self.update(e.data.selector, e.data.state, e.value);
+      }
+
+      // Cache for the states of this dependee.
+      this.values[selector] = {};
+
+      for (var i in dependeeStates) {
+        if (dependeeStates.hasOwnProperty(i)) {
+          state = dependeeStates[i];
+          // Make sure we're not initializing this selector/state combination
+          // twice.
+          if ($.inArray(state, dependeeStates) === -1) {
+            continue;
+          }
+
+          state = states.State.sanitize(state);
+
+          // Initialize the value of this state.
+          this.values[selector][state.name] = null;
+
+          // Monitor state changes of the specified state for this dependee.
+          $(selector).on('state:' + state, {selector: selector, state: state}, stateEventHandler);
+
+          // Make sure the event we just bound ourselves to is actually fired.
+          new states.Trigger({selector: selector, state: state});
+        }
+      }
+    },
+
+    /**
+     * Compares a value with a reference value.
+     *
+     * @memberof Drupal.states.Dependent#
+     *
+     * @param {object} reference
+     *   The value used for reference.
+     * @param {string} selector
+     *   CSS selector describing the dependee.
+     * @param {Drupal.states.State} state
+     *   A State object describing the dependee's updated state.
+     *
+     * @return {bool}
+     *   true or false.
+     */
+    compare: function (reference, selector, state) {
+      var value = this.values[selector][state.name];
+      if (reference.constructor.name in states.Dependent.comparisons) {
+        // Use a custom compare function for certain reference value types.
+        return states.Dependent.comparisons[reference.constructor.name](reference, value);
+      }
+      else {
+        // Do a plain comparison otherwise.
+        return compare(reference, value);
+      }
+    },
+
+    /**
+     * Update the value of a dependee's state.
+     *
+     * @memberof Drupal.states.Dependent#
+     *
+     * @param {string} selector
+     *   CSS selector describing the dependee.
+     * @param {Drupal.states.state} state
+     *   A State object describing the dependee's updated state.
+     * @param {string} value
+     *   The new value for the dependee's updated state.
+     */
+    update: function (selector, state, value) {
+      // Only act when the 'new' value is actually new.
+      if (value !== this.values[selector][state.name]) {
+        this.values[selector][state.name] = value;
+        this.reevaluate();
+      }
+    },
+
+    /**
+     * Triggers change events in case a state changed.
+     *
+     * @memberof Drupal.states.Dependent#
+     */
+    reevaluate: function () {
+      // Check whether any constraint for this dependent state is satisfied.
+      var value = this.verifyConstraints(this.constraints);
+
+      // Only invoke a state change event when the value actually changed.
+      if (value !== this.oldValue) {
+        // Store the new value so that we can compare later whether the value
+        // actually changed.
+        this.oldValue = value;
+
+        // Normalize the value to match the normalized state name.
+        value = invert(value, this.state.invert);
+
+        // By adding "trigger: true", we ensure that state changes don't go into
+        // infinite loops.
+        this.element.trigger({type: 'state:' + this.state, value: value, trigger: true});
+      }
+    },
+
+    /**
+     * Evaluates child constraints to determine if a constraint is satisfied.
+     *
+     * @memberof Drupal.states.Dependent#
+     *
+     * @param {object|Array} constraints
+     *   A constraint object or an array of constraints.
+     * @param {string} selector
+     *   The selector for these constraints. If undefined, there isn't yet a
+     *   selector that these constraints apply to. In that case, the keys of the
+     *   object are interpreted as the selector if encountered.
+     *
+     * @return {bool}
+     *   true or false, depending on whether these constraints are satisfied.
+     */
+    verifyConstraints: function (constraints, selector) {
+      var result;
+      if ($.isArray(constraints)) {
+        // This constraint is an array (OR or XOR).
+        var hasXor = $.inArray('xor', constraints) === -1;
+        var len = constraints.length;
+        for (var i = 0; i < len; i++) {
+          if (constraints[i] !== 'xor') {
+            var constraint = this.checkConstraints(constraints[i], selector, i);
+            // Return if this is OR and we have a satisfied constraint or if
+            // this is XOR and we have a second satisfied constraint.
+            if (constraint && (hasXor || result)) {
+              return hasXor;
+            }
+            result = result || constraint;
+          }
+        }
+      }
+      // Make sure we don't try to iterate over things other than objects. This
+      // shouldn't normally occur, but in case the condition definition is
+      // bogus, we don't want to end up with an infinite loop.
+      else if ($.isPlainObject(constraints)) {
+        // This constraint is an object (AND).
+        for (var n in constraints) {
+          if (constraints.hasOwnProperty(n)) {
+            result = ternary(result, this.checkConstraints(constraints[n], selector, n));
+            // False and anything else will evaluate to false, so return when
+            // any false condition is found.
+            if (result === false) { return false; }
+          }
+        }
+      }
+      return result;
+    },
+
+    /**
+     * Checks whether the value matches the requirements for this constraint.
+     *
+     * @memberof Drupal.states.Dependent#
+     *
+     * @param {string|Array|object} value
+     *   Either the value of a state or an array/object of constraints. In the
+     *   latter case, resolving the constraint continues.
+     * @param {string} [selector]
+     *   The selector for this constraint. If undefined, there isn't yet a
+     *   selector that this constraint applies to. In that case, the state key
+     *   is propagates to a selector and resolving continues.
+     * @param {Drupal.states.State} [state]
+     *   The state to check for this constraint. If undefined, resolving
+     *   continues. If both selector and state aren't undefined and valid
+     *   non-numeric strings, a lookup for the actual value of that selector's
+     *   state is performed. This parameter is not a State object but a pristine
+     *   state string.
+     *
+     * @return {bool}
+     *   true or false, depending on whether this constraint is satisfied.
+     */
+    checkConstraints: function (value, selector, state) {
+      // Normalize the last parameter. If it's non-numeric, we treat it either
+      // as a selector (in case there isn't one yet) or as a trigger/state.
+      if (typeof state !== 'string' || (/[0-9]/).test(state[0])) {
+        state = null;
+      }
+      else if (typeof selector === 'undefined') {
+        // Propagate the state to the selector when there isn't one yet.
+        selector = state;
+        state = null;
+      }
+
+      if (state !== null) {
+        // Constraints is the actual constraints of an element to check for.
+        state = states.State.sanitize(state);
+        return invert(this.compare(value, selector, state), state.invert);
+      }
+      else {
+        // Resolve this constraint as an AND/OR operator.
+        return this.verifyConstraints(value, selector);
+      }
+    },
+
+    /**
+     * Gathers information about all required triggers.
+     *
+     * @memberof Drupal.states.Dependent#
+     *
+     * @return {object}
+     *   An object describing the required triggers.
+     */
+    getDependees: function () {
+      var cache = {};
+      // Swivel the lookup function so that we can record all available
+      // selector- state combinations for initialization.
+      var _compare = this.compare;
+      this.compare = function (reference, selector, state) {
+        (cache[selector] || (cache[selector] = [])).push(state.name);
+        // Return nothing (=== undefined) so that the constraint loops are not
+        // broken.
+      };
+
+      // This call doesn't actually verify anything but uses the resolving
+      // mechanism to go through the constraints array, trying to look up each
+      // value. Since we swivelled the compare function, this comparison returns
+      // undefined and lookup continues until the very end. Instead of lookup up
+      // the value, we record that combination of selector and state so that we
+      // can initialize all triggers.
+      this.verifyConstraints(this.constraints);
+      // Restore the original function.
+      this.compare = _compare;
+
+      return cache;
+    }
+  };
+
+  /**
+   * @constructor Drupal.states.Trigger
+   *
+   * @param {object} args
+   *   Trigger arguments.
+   */
+  states.Trigger = function (args) {
+    $.extend(this, args);
+
+    if (this.state in states.Trigger.states) {
+      this.element = $(this.selector);
+
+      // Only call the trigger initializer when it wasn't yet attached to this
+      // element. Otherwise we'd end up with duplicate events.
+      if (!this.element.data('trigger:' + this.state)) {
+        this.initialize();
+      }
+    }
+  };
+
+  states.Trigger.prototype = {
+
+    /**
+     * @memberof Drupal.states.Trigger#
+     */
+    initialize: function () {
+      var trigger = states.Trigger.states[this.state];
+
+      if (typeof trigger === 'function') {
+        // We have a custom trigger initialization function.
+        trigger.call(window, this.element);
+      }
+      else {
+        for (var event in trigger) {
+          if (trigger.hasOwnProperty(event)) {
+            this.defaultTrigger(event, trigger[event]);
+          }
+        }
+      }
+
+      // Mark this trigger as initialized for this element.
+      this.element.data('trigger:' + this.state, true);
+    },
+
+    /**
+     * @memberof Drupal.states.Trigger#
+     *
+     * @param {jQuery.Event} event
+     *   The event triggered.
+     * @param {function} valueFn
+     *   The function to call.
+     */
+    defaultTrigger: function (event, valueFn) {
+      var oldValue = valueFn.call(this.element);
+
+      // Attach the event callback.
+      this.element.on(event, $.proxy(function (e) {
+        var value = valueFn.call(this.element, e);
+        // Only trigger the event if the value has actually changed.
+        if (oldValue !== value) {
+          this.element.trigger({type: 'state:' + this.state, value: value, oldValue: oldValue});
+          oldValue = value;
+        }
+      }, this));
+
+      states.postponed.push($.proxy(function () {
+        // Trigger the event once for initialization purposes.
+        this.element.trigger({type: 'state:' + this.state, value: oldValue, oldValue: null});
+      }, this));
+    }
+  };
+
+  /**
+   * This list of states contains functions that are used to monitor the state
+   * of an element. Whenever an element depends on the state of another element,
+   * one of these trigger functions is added to the dependee so that the
+   * dependent element can be updated.
+   *
+   * @name Drupal.states.Trigger.states
+   *
+   * @prop empty
+   * @prop checked
+   * @prop value
+   * @prop collapsed
+   */
+  states.Trigger.states = {
+    // 'empty' describes the state to be monitored.
+    empty: {
+      // 'keyup' is the (native DOM) event that we watch for.
+      keyup: function () {
+        // The function associated with that trigger returns the new value for
+        // the state.
+        return this.val() === '';
+      }
+    },
+
+    checked: {
+      change: function () {
+        // prop() and attr() only takes the first element into account. To
+        // support selectors matching multiple checkboxes, iterate over all and
+        // return whether any is checked.
+        var checked = false;
+        this.each(function () {
+          // Use prop() here as we want a boolean of the checkbox state.
+          // @see http://api.jquery.com/prop/
+          checked = $(this).prop('checked');
+          // Break the each() loop if this is checked.
+          return !checked;
+        });
+        return checked;
+      }
+    },
+
+    // For radio buttons, only return the value if the radio button is selected.
+    value: {
+      keyup: function () {
+        // Radio buttons share the same :input[name="key"] selector.
+        if (this.length > 1) {
+          // Initial checked value of radios is undefined, so we return false.
+          return this.filter(':checked').val() || false;
+        }
+        return this.val();
+      },
+      change: function () {
+        // Radio buttons share the same :input[name="key"] selector.
+        if (this.length > 1) {
+          // Initial checked value of radios is undefined, so we return false.
+          return this.filter(':checked').val() || false;
+        }
+        return this.val();
+      }
+    },
+
+    collapsed: {
+      collapsed: function (e) {
+        return (typeof e !== 'undefined' && 'value' in e) ? e.value : !this.is('[open]');
+      }
+    }
+  };
+
+  /**
+   * A state object is used for describing the state and performing aliasing.
+   *
+   * @constructor Drupal.states.State
+   *
+   * @param {string} state
+   *   The name of the state.
+   */
+  states.State = function (state) {
+
+    /**
+     * Original unresolved name.
+     */
+    this.pristine = this.name = state;
+
+    // Normalize the state name.
+    var process = true;
+    do {
+      // Iteratively remove exclamation marks and invert the value.
+      while (this.name.charAt(0) === '!') {
+        this.name = this.name.substring(1);
+        this.invert = !this.invert;
+      }
+
+      // Replace the state with its normalized name.
+      if (this.name in states.State.aliases) {
+        this.name = states.State.aliases[this.name];
+      }
+      else {
+        process = false;
+      }
+    } while (process);
+  };
+
+  /**
+   * Creates a new State object by sanitizing the passed value.
+   *
+   * @name Drupal.states.State.sanitize
+   *
+   * @param {string|Drupal.states.State} state
+   *   A state object or the name of a state.
+   *
+   * @return {Drupal.states.state}
+   *   A state object.
+   */
+  states.State.sanitize = function (state) {
+    if (state instanceof states.State) {
+      return state;
+    }
+    else {
+      return new states.State(state);
+    }
+  };
+
+  /**
+   * This list of aliases is used to normalize states and associates negated
+   * names with their respective inverse state.
+   *
+   * @name Drupal.states.State.aliases
+   */
+  states.State.aliases = {
+    enabled: '!disabled',
+    invisible: '!visible',
+    invalid: '!valid',
+    untouched: '!touched',
+    optional: '!required',
+    filled: '!empty',
+    unchecked: '!checked',
+    irrelevant: '!relevant',
+    expanded: '!collapsed',
+    open: '!collapsed',
+    closed: 'collapsed',
+    readwrite: '!readonly'
+  };
+
+  states.State.prototype = {
+
+    /**
+     * @memberof Drupal.states.State#
+     */
+    invert: false,
+
+    /**
+     * Ensures that just using the state object returns the name.
+     *
+     * @memberof Drupal.states.State#
+     *
+     * @return {string}
+     *   The name of the state.
+     */
+    toString: function () {
+      return this.name;
+    }
+  };
+
+  /**
+   * Global state change handlers. These are bound to "document" to cover all
+   * elements whose state changes. Events sent to elements within the page
+   * bubble up to these handlers. We use this system so that themes and modules
+   * can override these state change handlers for particular parts of a page.
+   */
+
+  var $document = $(document);
+  $document.on('state:disabled', function (e) {
+    // Only act when this change was triggered by a dependency and not by the
+    // element monitoring itself.
+    if (e.trigger) {
+      $(e.target)
+        .prop('disabled', e.value)
+        .closest('.js-form-item, .js-form-submit, .js-form-wrapper').toggleClass('form-disabled', e.value)
+        .find('select, input, textarea').prop('disabled', e.value);
+
+      // Note: WebKit nightlies don't reflect that change correctly.
+      // See https://bugs.webkit.org/show_bug.cgi?id=23789
+    }
+  });
+
+  $document.on('state:required', function (e) {
+    if (e.trigger) {
+      if (e.value) {
+        var label = 'label' + (e.target.id ? '[for=' + e.target.id + ']' : '');
+        var $label = $(e.target).attr({'required': 'required', 'aria-required': 'aria-required'}).closest('.js-form-item, .js-form-wrapper').find(label);
+        // Avoids duplicate required markers on initialization.
+        if (!$label.hasClass('js-form-required').length) {
+          $label.addClass('js-form-required form-required');
+        }
+      }
+      else {
+        $(e.target).removeAttr('required aria-required').closest('.js-form-item, .js-form-wrapper').find('label.js-form-required').removeClass('js-form-required form-required');
+      }
+    }
+  });
+
+  $document.on('state:visible', function (e) {
+    if (e.trigger) {
+      $(e.target).closest('.js-form-item, .js-form-submit, .js-form-wrapper').toggle(e.value);
+    }
+  });
+
+  $document.on('state:checked', function (e) {
+    if (e.trigger) {
+      $(e.target).prop('checked', e.value);
+    }
+  });
+
+  $document.on('state:collapsed', function (e) {
+    if (e.trigger) {
+      if ($(e.target).is('[open]') === e.value) {
+        $(e.target).find('> summary').trigger('click');
+      }
+    }
+  });
+
+  /**
+   * These are helper functions implementing addition "operators" and don't
+   * implement any logic that is particular to states.
+   */
+
+  /**
+   * Bitwise AND with a third undefined state.
+   *
+   * @function Drupal.states~ternary
+   *
+   * @param {*} a
+   *   Value a.
+   * @param {*} b
+   *   Value b
+   *
+   * @return {bool}
+   *   The result.
+   */
+  function ternary(a, b) {
+    if (typeof a === 'undefined') {
+      return b;
+    }
+    else if (typeof b === 'undefined') {
+      return a;
+    }
+    else {
+      return a && b;
+    }
+  }
+
+  /**
+   * Inverts a (if it's not undefined) when invertState is true.
+   *
+   * @function Drupal.states~invert
+   *
+   * @param {*} a
+   *   The value to maybe invert.
+   * @param {bool} invertState
+   *   Whether to invert state or not.
+   *
+   * @return {bool}
+   *   The result.
+   */
+  function invert(a, invertState) {
+    return (invertState && typeof a !== 'undefined') ? !a : a;
+  }
+
+  /**
+   * Compares two values while ignoring undefined values.
+   *
+   * @function Drupal.states~compare
+   *
+   * @param {*} a
+   *   Value a.
+   * @param {*} b
+   *   Value b.
+   *
+   * @return {bool}
+   *   The comparison result.
+   */
+  function compare(a, b) {
+    if (a === b) {
+      return typeof a === 'undefined' ? a : true;
+    }
+    else {
+      return typeof a === 'undefined' || typeof b === 'undefined';
+    }
+  }
+
+})(jQuery, Drupal);
diff --git a/core/misc/states.js b/core/misc/states.js
index 24374b625f7a..0354df4b4027 100644
--- a/core/misc/states.js
+++ b/core/misc/states.js
@@ -1,38 +1,21 @@
 /**
- * @file
- * Drupal's states library.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./misc/states.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   * The base States namespace.
-   *
-   * Having the local states variable allows us to use the States namespace
-   * without having to always declare "Drupal.states".
-   *
-   * @namespace Drupal.states
-   */
   var states = Drupal.states = {
-
-    /**
-     * An array of functions that should be postponed.
-     */
     postponed: []
   };
 
-  /**
-   * Attaches the states.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches states behaviors.
-   */
   Drupal.behaviors.states = {
-    attach: function (context, settings) {
+    attach: function attach(context, settings) {
       var $states = $(context).find('[data-drupal-states]');
       var config;
       var state;
@@ -50,31 +33,14 @@
         }
       }
 
-      // Execute all postponed functions now.
       while (states.postponed.length) {
-        (states.postponed.shift())();
+        states.postponed.shift()();
       }
     }
   };
 
-  /**
-   * Object representing an element that depends on other elements.
-   *
-   * @constructor Drupal.states.Dependent
-   *
-   * @param {object} args
-   *   Object with the following keys (all of which are required)
-   * @param {jQuery} args.element
-   *   A jQuery object of the dependent element
-   * @param {Drupal.states.State} args.state
-   *   A State object describing the state that is dependent
-   * @param {object} args.constraints
-   *   An object with dependency specifications. Lists all elements that this
-   *   element depends on. It can be nested and can contain
-   *   arbitrary AND and OR clauses.
-   */
   states.Dependent = function (args) {
-    $.extend(this, {values: {}, oldValue: null}, args);
+    $.extend(this, { values: {}, oldValue: null }, args);
 
     this.dependees = this.getDependees();
     for (var selector in this.dependees) {
@@ -84,49 +50,20 @@
     }
   };
 
-  /**
-   * Comparison functions for comparing the value of an element with the
-   * specification from the dependency settings. If the object type can't be
-   * found in this list, the === operator is used by default.
-   *
-   * @name Drupal.states.Dependent.comparisons
-   *
-   * @prop {function} RegExp
-   * @prop {function} Function
-   * @prop {function} Number
-   */
   states.Dependent.comparisons = {
-    RegExp: function (reference, value) {
+    RegExp: function RegExp(reference, value) {
       return reference.test(value);
     },
-    Function: function (reference, value) {
-      // The "reference" variable is a comparison function.
+    Function: function Function(reference, value) {
       return reference(value);
     },
-    Number: function (reference, value) {
-      // If "reference" is a number and "value" is a string, then cast
-      // reference as a string before applying the strict comparison in
-      // compare().
-      // Otherwise numeric keys in the form's #states array fail to match
-      // string values returned from jQuery's val().
-      return (typeof value === 'string') ? compare(reference.toString(), value) : compare(reference, value);
+    Number: function Number(reference, value) {
+      return typeof value === 'string' ? _compare2(reference.toString(), value) : _compare2(reference, value);
     }
   };
 
   states.Dependent.prototype = {
-
-    /**
-     * Initializes one of the elements this dependent depends on.
-     *
-     * @memberof Drupal.states.Dependent#
-     *
-     * @param {string} selector
-     *   The CSS selector describing the dependee.
-     * @param {object} dependeeStates
-     *   The list of states that have to be monitored for tracking the
-     *   dependee's compliance status.
-     */
-    initializeDependee: function (selector, dependeeStates) {
+    initializeDependee: function initializeDependee(selector, dependeeStates) {
       var state;
       var self = this;
 
@@ -134,245 +71,122 @@
         self.update(e.data.selector, e.data.state, e.value);
       }
 
-      // Cache for the states of this dependee.
       this.values[selector] = {};
 
       for (var i in dependeeStates) {
         if (dependeeStates.hasOwnProperty(i)) {
           state = dependeeStates[i];
-          // Make sure we're not initializing this selector/state combination
-          // twice.
+
           if ($.inArray(state, dependeeStates) === -1) {
             continue;
           }
 
           state = states.State.sanitize(state);
 
-          // Initialize the value of this state.
           this.values[selector][state.name] = null;
 
-          // Monitor state changes of the specified state for this dependee.
-          $(selector).on('state:' + state, {selector: selector, state: state}, stateEventHandler);
+          $(selector).on('state:' + state, { selector: selector, state: state }, stateEventHandler);
 
-          // Make sure the event we just bound ourselves to is actually fired.
-          new states.Trigger({selector: selector, state: state});
+          new states.Trigger({ selector: selector, state: state });
         }
       }
     },
 
-    /**
-     * Compares a value with a reference value.
-     *
-     * @memberof Drupal.states.Dependent#
-     *
-     * @param {object} reference
-     *   The value used for reference.
-     * @param {string} selector
-     *   CSS selector describing the dependee.
-     * @param {Drupal.states.State} state
-     *   A State object describing the dependee's updated state.
-     *
-     * @return {bool}
-     *   true or false.
-     */
-    compare: function (reference, selector, state) {
+    compare: function compare(reference, selector, state) {
       var value = this.values[selector][state.name];
       if (reference.constructor.name in states.Dependent.comparisons) {
-        // Use a custom compare function for certain reference value types.
         return states.Dependent.comparisons[reference.constructor.name](reference, value);
-      }
-      else {
-        // Do a plain comparison otherwise.
-        return compare(reference, value);
+      } else {
+        return _compare2(reference, value);
       }
     },
 
-    /**
-     * Update the value of a dependee's state.
-     *
-     * @memberof Drupal.states.Dependent#
-     *
-     * @param {string} selector
-     *   CSS selector describing the dependee.
-     * @param {Drupal.states.state} state
-     *   A State object describing the dependee's updated state.
-     * @param {string} value
-     *   The new value for the dependee's updated state.
-     */
-    update: function (selector, state, value) {
-      // Only act when the 'new' value is actually new.
+    update: function update(selector, state, value) {
       if (value !== this.values[selector][state.name]) {
         this.values[selector][state.name] = value;
         this.reevaluate();
       }
     },
 
-    /**
-     * Triggers change events in case a state changed.
-     *
-     * @memberof Drupal.states.Dependent#
-     */
-    reevaluate: function () {
-      // Check whether any constraint for this dependent state is satisfied.
+    reevaluate: function reevaluate() {
       var value = this.verifyConstraints(this.constraints);
 
-      // Only invoke a state change event when the value actually changed.
       if (value !== this.oldValue) {
-        // Store the new value so that we can compare later whether the value
-        // actually changed.
         this.oldValue = value;
 
-        // Normalize the value to match the normalized state name.
         value = invert(value, this.state.invert);
 
-        // By adding "trigger: true", we ensure that state changes don't go into
-        // infinite loops.
-        this.element.trigger({type: 'state:' + this.state, value: value, trigger: true});
+        this.element.trigger({ type: 'state:' + this.state, value: value, trigger: true });
       }
     },
 
-    /**
-     * Evaluates child constraints to determine if a constraint is satisfied.
-     *
-     * @memberof Drupal.states.Dependent#
-     *
-     * @param {object|Array} constraints
-     *   A constraint object or an array of constraints.
-     * @param {string} selector
-     *   The selector for these constraints. If undefined, there isn't yet a
-     *   selector that these constraints apply to. In that case, the keys of the
-     *   object are interpreted as the selector if encountered.
-     *
-     * @return {bool}
-     *   true or false, depending on whether these constraints are satisfied.
-     */
-    verifyConstraints: function (constraints, selector) {
+    verifyConstraints: function verifyConstraints(constraints, selector) {
       var result;
       if ($.isArray(constraints)) {
-        // This constraint is an array (OR or XOR).
         var hasXor = $.inArray('xor', constraints) === -1;
         var len = constraints.length;
         for (var i = 0; i < len; i++) {
           if (constraints[i] !== 'xor') {
             var constraint = this.checkConstraints(constraints[i], selector, i);
-            // Return if this is OR and we have a satisfied constraint or if
-            // this is XOR and we have a second satisfied constraint.
+
             if (constraint && (hasXor || result)) {
               return hasXor;
             }
             result = result || constraint;
           }
         }
-      }
-      // Make sure we don't try to iterate over things other than objects. This
-      // shouldn't normally occur, but in case the condition definition is
-      // bogus, we don't want to end up with an infinite loop.
-      else if ($.isPlainObject(constraints)) {
-        // This constraint is an object (AND).
-        for (var n in constraints) {
-          if (constraints.hasOwnProperty(n)) {
-            result = ternary(result, this.checkConstraints(constraints[n], selector, n));
-            // False and anything else will evaluate to false, so return when
-            // any false condition is found.
-            if (result === false) { return false; }
+      } else if ($.isPlainObject(constraints)) {
+          for (var n in constraints) {
+            if (constraints.hasOwnProperty(n)) {
+              result = ternary(result, this.checkConstraints(constraints[n], selector, n));
+
+              if (result === false) {
+                return false;
+              }
+            }
           }
         }
-      }
       return result;
     },
 
-    /**
-     * Checks whether the value matches the requirements for this constraint.
-     *
-     * @memberof Drupal.states.Dependent#
-     *
-     * @param {string|Array|object} value
-     *   Either the value of a state or an array/object of constraints. In the
-     *   latter case, resolving the constraint continues.
-     * @param {string} [selector]
-     *   The selector for this constraint. If undefined, there isn't yet a
-     *   selector that this constraint applies to. In that case, the state key
-     *   is propagates to a selector and resolving continues.
-     * @param {Drupal.states.State} [state]
-     *   The state to check for this constraint. If undefined, resolving
-     *   continues. If both selector and state aren't undefined and valid
-     *   non-numeric strings, a lookup for the actual value of that selector's
-     *   state is performed. This parameter is not a State object but a pristine
-     *   state string.
-     *
-     * @return {bool}
-     *   true or false, depending on whether this constraint is satisfied.
-     */
-    checkConstraints: function (value, selector, state) {
-      // Normalize the last parameter. If it's non-numeric, we treat it either
-      // as a selector (in case there isn't one yet) or as a trigger/state.
-      if (typeof state !== 'string' || (/[0-9]/).test(state[0])) {
+    checkConstraints: function checkConstraints(value, selector, state) {
+      if (typeof state !== 'string' || /[0-9]/.test(state[0])) {
         state = null;
-      }
-      else if (typeof selector === 'undefined') {
-        // Propagate the state to the selector when there isn't one yet.
+      } else if (typeof selector === 'undefined') {
         selector = state;
         state = null;
       }
 
       if (state !== null) {
-        // Constraints is the actual constraints of an element to check for.
         state = states.State.sanitize(state);
         return invert(this.compare(value, selector, state), state.invert);
-      }
-      else {
-        // Resolve this constraint as an AND/OR operator.
+      } else {
         return this.verifyConstraints(value, selector);
       }
     },
 
-    /**
-     * Gathers information about all required triggers.
-     *
-     * @memberof Drupal.states.Dependent#
-     *
-     * @return {object}
-     *   An object describing the required triggers.
-     */
-    getDependees: function () {
+    getDependees: function getDependees() {
       var cache = {};
-      // Swivel the lookup function so that we can record all available
-      // selector- state combinations for initialization.
+
       var _compare = this.compare;
       this.compare = function (reference, selector, state) {
         (cache[selector] || (cache[selector] = [])).push(state.name);
-        // Return nothing (=== undefined) so that the constraint loops are not
-        // broken.
       };
 
-      // This call doesn't actually verify anything but uses the resolving
-      // mechanism to go through the constraints array, trying to look up each
-      // value. Since we swivelled the compare function, this comparison returns
-      // undefined and lookup continues until the very end. Instead of lookup up
-      // the value, we record that combination of selector and state so that we
-      // can initialize all triggers.
       this.verifyConstraints(this.constraints);
-      // Restore the original function.
+
       this.compare = _compare;
 
       return cache;
     }
   };
 
-  /**
-   * @constructor Drupal.states.Trigger
-   *
-   * @param {object} args
-   *   Trigger arguments.
-   */
   states.Trigger = function (args) {
     $.extend(this, args);
 
     if (this.state in states.Trigger.states) {
       this.element = $(this.selector);
 
-      // Only call the trigger initializer when it wasn't yet attached to this
-      // element. Otherwise we'd end up with duplicate events.
       if (!this.element.data('trigger:' + this.state)) {
         this.initialize();
       }
@@ -380,18 +194,12 @@
   };
 
   states.Trigger.prototype = {
-
-    /**
-     * @memberof Drupal.states.Trigger#
-     */
-    initialize: function () {
+    initialize: function initialize() {
       var trigger = states.Trigger.states[this.state];
 
       if (typeof trigger === 'function') {
-        // We have a custom trigger initialization function.
         trigger.call(window, this.element);
-      }
-      else {
+      } else {
         for (var event in trigger) {
           if (trigger.hasOwnProperty(event)) {
             this.defaultTrigger(event, trigger[event]);
@@ -399,93 +207,55 @@
         }
       }
 
-      // Mark this trigger as initialized for this element.
       this.element.data('trigger:' + this.state, true);
     },
 
-    /**
-     * @memberof Drupal.states.Trigger#
-     *
-     * @param {jQuery.Event} event
-     *   The event triggered.
-     * @param {function} valueFn
-     *   The function to call.
-     */
-    defaultTrigger: function (event, valueFn) {
+    defaultTrigger: function defaultTrigger(event, valueFn) {
       var oldValue = valueFn.call(this.element);
 
-      // Attach the event callback.
       this.element.on(event, $.proxy(function (e) {
         var value = valueFn.call(this.element, e);
-        // Only trigger the event if the value has actually changed.
+
         if (oldValue !== value) {
-          this.element.trigger({type: 'state:' + this.state, value: value, oldValue: oldValue});
+          this.element.trigger({ type: 'state:' + this.state, value: value, oldValue: oldValue });
           oldValue = value;
         }
       }, this));
 
       states.postponed.push($.proxy(function () {
-        // Trigger the event once for initialization purposes.
-        this.element.trigger({type: 'state:' + this.state, value: oldValue, oldValue: null});
+        this.element.trigger({ type: 'state:' + this.state, value: oldValue, oldValue: null });
       }, this));
     }
   };
 
-  /**
-   * This list of states contains functions that are used to monitor the state
-   * of an element. Whenever an element depends on the state of another element,
-   * one of these trigger functions is added to the dependee so that the
-   * dependent element can be updated.
-   *
-   * @name Drupal.states.Trigger.states
-   *
-   * @prop empty
-   * @prop checked
-   * @prop value
-   * @prop collapsed
-   */
   states.Trigger.states = {
-    // 'empty' describes the state to be monitored.
     empty: {
-      // 'keyup' is the (native DOM) event that we watch for.
-      keyup: function () {
-        // The function associated with that trigger returns the new value for
-        // the state.
+      keyup: function keyup() {
         return this.val() === '';
       }
     },
 
     checked: {
-      change: function () {
-        // prop() and attr() only takes the first element into account. To
-        // support selectors matching multiple checkboxes, iterate over all and
-        // return whether any is checked.
+      change: function change() {
         var checked = false;
         this.each(function () {
-          // Use prop() here as we want a boolean of the checkbox state.
-          // @see http://api.jquery.com/prop/
           checked = $(this).prop('checked');
-          // Break the each() loop if this is checked.
+
           return !checked;
         });
         return checked;
       }
     },
 
-    // For radio buttons, only return the value if the radio button is selected.
     value: {
-      keyup: function () {
-        // Radio buttons share the same :input[name="key"] selector.
+      keyup: function keyup() {
         if (this.length > 1) {
-          // Initial checked value of radios is undefined, so we return false.
           return this.filter(':checked').val() || false;
         }
         return this.val();
       },
-      change: function () {
-        // Radio buttons share the same :input[name="key"] selector.
+      change: function change() {
         if (this.length > 1) {
-          // Initial checked value of radios is undefined, so we return false.
           return this.filter(':checked').val() || false;
         }
         return this.val();
@@ -493,72 +263,38 @@
     },
 
     collapsed: {
-      collapsed: function (e) {
-        return (typeof e !== 'undefined' && 'value' in e) ? e.value : !this.is('[open]');
+      collapsed: function collapsed(e) {
+        return typeof e !== 'undefined' && 'value' in e ? e.value : !this.is('[open]');
       }
     }
   };
 
-  /**
-   * A state object is used for describing the state and performing aliasing.
-   *
-   * @constructor Drupal.states.State
-   *
-   * @param {string} state
-   *   The name of the state.
-   */
   states.State = function (state) {
-
-    /**
-     * Original unresolved name.
-     */
     this.pristine = this.name = state;
 
-    // Normalize the state name.
     var process = true;
     do {
-      // Iteratively remove exclamation marks and invert the value.
       while (this.name.charAt(0) === '!') {
         this.name = this.name.substring(1);
         this.invert = !this.invert;
       }
 
-      // Replace the state with its normalized name.
       if (this.name in states.State.aliases) {
         this.name = states.State.aliases[this.name];
-      }
-      else {
+      } else {
         process = false;
       }
     } while (process);
   };
 
-  /**
-   * Creates a new State object by sanitizing the passed value.
-   *
-   * @name Drupal.states.State.sanitize
-   *
-   * @param {string|Drupal.states.State} state
-   *   A state object or the name of a state.
-   *
-   * @return {Drupal.states.state}
-   *   A state object.
-   */
   states.State.sanitize = function (state) {
     if (state instanceof states.State) {
       return state;
-    }
-    else {
+    } else {
       return new states.State(state);
     }
   };
 
-  /**
-   * This list of aliases is used to normalize states and associates negated
-   * names with their respective inverse state.
-   *
-   * @name Drupal.states.State.aliases
-   */
   states.State.aliases = {
     enabled: '!disabled',
     invisible: '!visible',
@@ -575,44 +311,17 @@
   };
 
   states.State.prototype = {
-
-    /**
-     * @memberof Drupal.states.State#
-     */
     invert: false,
 
-    /**
-     * Ensures that just using the state object returns the name.
-     *
-     * @memberof Drupal.states.State#
-     *
-     * @return {string}
-     *   The name of the state.
-     */
-    toString: function () {
+    toString: function toString() {
       return this.name;
     }
   };
 
-  /**
-   * Global state change handlers. These are bound to "document" to cover all
-   * elements whose state changes. Events sent to elements within the page
-   * bubble up to these handlers. We use this system so that themes and modules
-   * can override these state change handlers for particular parts of a page.
-   */
-
   var $document = $(document);
   $document.on('state:disabled', function (e) {
-    // Only act when this change was triggered by a dependency and not by the
-    // element monitoring itself.
     if (e.trigger) {
-      $(e.target)
-        .prop('disabled', e.value)
-        .closest('.js-form-item, .js-form-submit, .js-form-wrapper').toggleClass('form-disabled', e.value)
-        .find('select, input, textarea').prop('disabled', e.value);
-
-      // Note: WebKit nightlies don't reflect that change correctly.
-      // See https://bugs.webkit.org/show_bug.cgi?id=23789
+      $(e.target).prop('disabled', e.value).closest('.js-form-item, .js-form-submit, .js-form-wrapper').toggleClass('form-disabled', e.value).find('select, input, textarea').prop('disabled', e.value);
     }
   });
 
@@ -620,13 +329,12 @@
     if (e.trigger) {
       if (e.value) {
         var label = 'label' + (e.target.id ? '[for=' + e.target.id + ']' : '');
-        var $label = $(e.target).attr({'required': 'required', 'aria-required': 'aria-required'}).closest('.js-form-item, .js-form-wrapper').find(label);
-        // Avoids duplicate required markers on initialization.
+        var $label = $(e.target).attr({ 'required': 'required', 'aria-required': 'aria-required' }).closest('.js-form-item, .js-form-wrapper').find(label);
+
         if (!$label.hasClass('js-form-required').length) {
           $label.addClass('js-form-required form-required');
         }
-      }
-      else {
+      } else {
         $(e.target).removeAttr('required aria-required').closest('.js-form-item, .js-form-wrapper').find('label.js-form-required').removeClass('js-form-required form-required');
       }
     }
@@ -652,73 +360,25 @@
     }
   });
 
-  /**
-   * These are helper functions implementing addition "operators" and don't
-   * implement any logic that is particular to states.
-   */
-
-  /**
-   * Bitwise AND with a third undefined state.
-   *
-   * @function Drupal.states~ternary
-   *
-   * @param {*} a
-   *   Value a.
-   * @param {*} b
-   *   Value b
-   *
-   * @return {bool}
-   *   The result.
-   */
   function ternary(a, b) {
     if (typeof a === 'undefined') {
       return b;
-    }
-    else if (typeof b === 'undefined') {
+    } else if (typeof b === 'undefined') {
       return a;
-    }
-    else {
+    } else {
       return a && b;
     }
   }
 
-  /**
-   * Inverts a (if it's not undefined) when invertState is true.
-   *
-   * @function Drupal.states~invert
-   *
-   * @param {*} a
-   *   The value to maybe invert.
-   * @param {bool} invertState
-   *   Whether to invert state or not.
-   *
-   * @return {bool}
-   *   The result.
-   */
   function invert(a, invertState) {
-    return (invertState && typeof a !== 'undefined') ? !a : a;
+    return invertState && typeof a !== 'undefined' ? !a : a;
   }
 
-  /**
-   * Compares two values while ignoring undefined values.
-   *
-   * @function Drupal.states~compare
-   *
-   * @param {*} a
-   *   Value a.
-   * @param {*} b
-   *   Value b.
-   *
-   * @return {bool}
-   *   The comparison result.
-   */
-  function compare(a, b) {
+  function _compare2(a, b) {
     if (a === b) {
       return typeof a === 'undefined' ? a : true;
-    }
-    else {
+    } else {
       return typeof a === 'undefined' || typeof b === 'undefined';
     }
   }
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/misc/tabbingmanager.es6.js b/core/misc/tabbingmanager.es6.js
new file mode 100644
index 000000000000..134523f5bf97
--- /dev/null
+++ b/core/misc/tabbingmanager.es6.js
@@ -0,0 +1,369 @@
+/**
+ * @file
+ * Manages page tabbing modifications made by modules.
+ */
+
+/**
+ * Allow modules to respond to the constrain event.
+ *
+ * @event drupalTabbingConstrained
+ */
+
+/**
+ * Allow modules to respond to the tabbingContext release event.
+ *
+ * @event drupalTabbingContextReleased
+ */
+
+/**
+ * Allow modules to respond to the constrain event.
+ *
+ * @event drupalTabbingContextActivated
+ */
+
+/**
+ * Allow modules to respond to the constrain event.
+ *
+ * @event drupalTabbingContextDeactivated
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * Provides an API for managing page tabbing order modifications.
+   *
+   * @constructor Drupal~TabbingManager
+   */
+  function TabbingManager() {
+
+    /**
+     * Tabbing sets are stored as a stack. The active set is at the top of the
+     * stack. We use a JavaScript array as if it were a stack; we consider the
+     * first element to be the bottom and the last element to be the top. This
+     * allows us to use JavaScript's built-in Array.push() and Array.pop()
+     * methods.
+     *
+     * @type {Array.<Drupal~TabbingContext>}
+     */
+    this.stack = [];
+  }
+
+  /**
+   * Add public methods to the TabbingManager class.
+   */
+  $.extend(TabbingManager.prototype, /** @lends Drupal~TabbingManager# */{
+
+    /**
+     * Constrain tabbing to the specified set of elements only.
+     *
+     * Makes elements outside of the specified set of elements unreachable via
+     * the tab key.
+     *
+     * @param {jQuery} elements
+     *   The set of elements to which tabbing should be constrained. Can also
+     *   be a jQuery-compatible selector string.
+     *
+     * @return {Drupal~TabbingContext}
+     *   The TabbingContext instance.
+     *
+     * @fires event:drupalTabbingConstrained
+     */
+    constrain: function (elements) {
+      // Deactivate all tabbingContexts to prepare for the new constraint. A
+      // tabbingContext instance will only be reactivated if the stack is
+      // unwound to it in the _unwindStack() method.
+      var il = this.stack.length;
+      for (var i = 0; i < il; i++) {
+        this.stack[i].deactivate();
+      }
+
+      // The "active tabbing set" are the elements tabbing should be constrained
+      // to.
+      var $elements = $(elements).find(':tabbable').addBack(':tabbable');
+
+      var tabbingContext = new TabbingContext({
+        // The level is the current height of the stack before this new
+        // tabbingContext is pushed on top of the stack.
+        level: this.stack.length,
+        $tabbableElements: $elements
+      });
+
+      this.stack.push(tabbingContext);
+
+      // Activates the tabbingContext; this will manipulate the DOM to constrain
+      // tabbing.
+      tabbingContext.activate();
+
+      // Allow modules to respond to the constrain event.
+      $(document).trigger('drupalTabbingConstrained', tabbingContext);
+
+      return tabbingContext;
+    },
+
+    /**
+     * Restores a former tabbingContext when an active one is released.
+     *
+     * The TabbingManager stack of tabbingContext instances will be unwound
+     * from the top-most released tabbingContext down to the first non-released
+     * tabbingContext instance. This non-released instance is then activated.
+     */
+    release: function () {
+      // Unwind as far as possible: find the topmost non-released
+      // tabbingContext.
+      var toActivate = this.stack.length - 1;
+      while (toActivate >= 0 && this.stack[toActivate].released) {
+        toActivate--;
+      }
+
+      // Delete all tabbingContexts after the to be activated one. They have
+      // already been deactivated, so their effect on the DOM has been reversed.
+      this.stack.splice(toActivate + 1);
+
+      // Get topmost tabbingContext, if one exists, and activate it.
+      if (toActivate >= 0) {
+        this.stack[toActivate].activate();
+      }
+    },
+
+    /**
+     * Makes all elements outside of the tabbingContext's set untabbable.
+     *
+     * Elements made untabbable have their original tabindex and autofocus
+     * values stored so that they might be restored later when this
+     * tabbingContext is deactivated.
+     *
+     * @param {Drupal~TabbingContext} tabbingContext
+     *   The TabbingContext instance that has been activated.
+     */
+    activate: function (tabbingContext) {
+      var $set = tabbingContext.$tabbableElements;
+      var level = tabbingContext.level;
+      // Determine which elements are reachable via tabbing by default.
+      var $disabledSet = $(':tabbable')
+        // Exclude elements of the active tabbing set.
+        .not($set);
+      // Set the disabled set on the tabbingContext.
+      tabbingContext.$disabledElements = $disabledSet;
+      // Record the tabindex for each element, so we can restore it later.
+      var il = $disabledSet.length;
+      for (var i = 0; i < il; i++) {
+        this.recordTabindex($disabledSet.eq(i), level);
+      }
+      // Make all tabbable elements outside of the active tabbing set
+      // unreachable.
+      $disabledSet
+        .prop('tabindex', -1)
+        .prop('autofocus', false);
+
+      // Set focus on an element in the tabbingContext's set of tabbable
+      // elements. First, check if there is an element with an autofocus
+      // attribute. Select the last one from the DOM order.
+      var $hasFocus = $set.filter('[autofocus]').eq(-1);
+      // If no element in the tabbable set has an autofocus attribute, select
+      // the first element in the set.
+      if ($hasFocus.length === 0) {
+        $hasFocus = $set.eq(0);
+      }
+      $hasFocus.trigger('focus');
+    },
+
+    /**
+     * Restores that tabbable state of a tabbingContext's disabled elements.
+     *
+     * Elements that were made untabbable have their original tabindex and
+     * autofocus values restored.
+     *
+     * @param {Drupal~TabbingContext} tabbingContext
+     *   The TabbingContext instance that has been deactivated.
+     */
+    deactivate: function (tabbingContext) {
+      var $set = tabbingContext.$disabledElements;
+      var level = tabbingContext.level;
+      var il = $set.length;
+      for (var i = 0; i < il; i++) {
+        this.restoreTabindex($set.eq(i), level);
+      }
+    },
+
+    /**
+     * Records the tabindex and autofocus values of an untabbable element.
+     *
+     * @param {jQuery} $el
+     *   The set of elements that have been disabled.
+     * @param {number} level
+     *   The stack level for which the tabindex attribute should be recorded.
+     */
+    recordTabindex: function ($el, level) {
+      var tabInfo = $el.data('drupalOriginalTabIndices') || {};
+      tabInfo[level] = {
+        tabindex: $el[0].getAttribute('tabindex'),
+        autofocus: $el[0].hasAttribute('autofocus')
+      };
+      $el.data('drupalOriginalTabIndices', tabInfo);
+    },
+
+    /**
+     * Restores the tabindex and autofocus values of a reactivated element.
+     *
+     * @param {jQuery} $el
+     *   The element that is being reactivated.
+     * @param {number} level
+     *   The stack level for which the tabindex attribute should be restored.
+     */
+    restoreTabindex: function ($el, level) {
+      var tabInfo = $el.data('drupalOriginalTabIndices');
+      if (tabInfo && tabInfo[level]) {
+        var data = tabInfo[level];
+        if (data.tabindex) {
+          $el[0].setAttribute('tabindex', data.tabindex);
+        }
+        // If the element did not have a tabindex at this stack level then
+        // remove it.
+        else {
+          $el[0].removeAttribute('tabindex');
+        }
+        if (data.autofocus) {
+          $el[0].setAttribute('autofocus', 'autofocus');
+        }
+
+        // Clean up $.data.
+        if (level === 0) {
+          // Remove all data.
+          $el.removeData('drupalOriginalTabIndices');
+        }
+        else {
+          // Remove the data for this stack level and higher.
+          var levelToDelete = level;
+          while (tabInfo.hasOwnProperty(levelToDelete)) {
+            delete tabInfo[levelToDelete];
+            levelToDelete++;
+          }
+          $el.data('drupalOriginalTabIndices', tabInfo);
+        }
+      }
+    }
+  });
+
+  /**
+   * Stores a set of tabbable elements.
+   *
+   * This constraint can be removed with the release() method.
+   *
+   * @constructor Drupal~TabbingContext
+   *
+   * @param {object} options
+   *   A set of initiating values
+   * @param {number} options.level
+   *   The level in the TabbingManager's stack of this tabbingContext.
+   * @param {jQuery} options.$tabbableElements
+   *   The DOM elements that should be reachable via the tab key when this
+   *   tabbingContext is active.
+   * @param {jQuery} options.$disabledElements
+   *   The DOM elements that should not be reachable via the tab key when this
+   *   tabbingContext is active.
+   * @param {bool} options.released
+   *   A released tabbingContext can never be activated again. It will be
+   *   cleaned up when the TabbingManager unwinds its stack.
+   * @param {bool} options.active
+   *   When true, the tabbable elements of this tabbingContext will be reachable
+   *   via the tab key and the disabled elements will not. Only one
+   *   tabbingContext can be active at a time.
+   */
+  function TabbingContext(options) {
+
+    $.extend(this, /** @lends Drupal~TabbingContext# */{
+
+      /**
+       * @type {?number}
+       */
+      level: null,
+
+      /**
+       * @type {jQuery}
+       */
+      $tabbableElements: $(),
+
+      /**
+       * @type {jQuery}
+       */
+      $disabledElements: $(),
+
+      /**
+       * @type {bool}
+       */
+      released: false,
+
+      /**
+       * @type {bool}
+       */
+      active: false
+    }, options);
+  }
+
+  /**
+   * Add public methods to the TabbingContext class.
+   */
+  $.extend(TabbingContext.prototype, /** @lends Drupal~TabbingContext# */{
+
+    /**
+     * Releases this TabbingContext.
+     *
+     * Once a TabbingContext object is released, it can never be activated
+     * again.
+     *
+     * @fires event:drupalTabbingContextReleased
+     */
+    release: function () {
+      if (!this.released) {
+        this.deactivate();
+        this.released = true;
+        Drupal.tabbingManager.release(this);
+        // Allow modules to respond to the tabbingContext release event.
+        $(document).trigger('drupalTabbingContextReleased', this);
+      }
+    },
+
+    /**
+     * Activates this TabbingContext.
+     *
+     * @fires event:drupalTabbingContextActivated
+     */
+    activate: function () {
+      // A released TabbingContext object can never be activated again.
+      if (!this.active && !this.released) {
+        this.active = true;
+        Drupal.tabbingManager.activate(this);
+        // Allow modules to respond to the constrain event.
+        $(document).trigger('drupalTabbingContextActivated', this);
+      }
+    },
+
+    /**
+     * Deactivates this TabbingContext.
+     *
+     * @fires event:drupalTabbingContextDeactivated
+     */
+    deactivate: function () {
+      if (this.active) {
+        this.active = false;
+        Drupal.tabbingManager.deactivate(this);
+        // Allow modules to respond to the constrain event.
+        $(document).trigger('drupalTabbingContextDeactivated', this);
+      }
+    }
+  });
+
+  // Mark this behavior as processed on the first pass and return if it is
+  // already processed.
+  if (Drupal.tabbingManager) {
+    return;
+  }
+
+  /**
+   * @type {Drupal~TabbingManager}
+   */
+  Drupal.tabbingManager = new TabbingManager();
+
+}(jQuery, Drupal));
diff --git a/core/misc/tabbingmanager.js b/core/misc/tabbingmanager.js
index 134523f5bf97..48aa40804e0e 100644
--- a/core/misc/tabbingmanager.js
+++ b/core/misc/tabbingmanager.js
@@ -1,184 +1,79 @@
 /**
- * @file
- * Manages page tabbing modifications made by modules.
- */
-
-/**
- * Allow modules to respond to the constrain event.
- *
- * @event drupalTabbingConstrained
- */
-
-/**
- * Allow modules to respond to the tabbingContext release event.
- *
- * @event drupalTabbingContextReleased
- */
-
-/**
- * Allow modules to respond to the constrain event.
- *
- * @event drupalTabbingContextActivated
- */
-
-/**
- * Allow modules to respond to the constrain event.
- *
- * @event drupalTabbingContextDeactivated
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./misc/tabbingmanager.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   * Provides an API for managing page tabbing order modifications.
-   *
-   * @constructor Drupal~TabbingManager
-   */
   function TabbingManager() {
-
-    /**
-     * Tabbing sets are stored as a stack. The active set is at the top of the
-     * stack. We use a JavaScript array as if it were a stack; we consider the
-     * first element to be the bottom and the last element to be the top. This
-     * allows us to use JavaScript's built-in Array.push() and Array.pop()
-     * methods.
-     *
-     * @type {Array.<Drupal~TabbingContext>}
-     */
     this.stack = [];
   }
 
-  /**
-   * Add public methods to the TabbingManager class.
-   */
-  $.extend(TabbingManager.prototype, /** @lends Drupal~TabbingManager# */{
-
-    /**
-     * Constrain tabbing to the specified set of elements only.
-     *
-     * Makes elements outside of the specified set of elements unreachable via
-     * the tab key.
-     *
-     * @param {jQuery} elements
-     *   The set of elements to which tabbing should be constrained. Can also
-     *   be a jQuery-compatible selector string.
-     *
-     * @return {Drupal~TabbingContext}
-     *   The TabbingContext instance.
-     *
-     * @fires event:drupalTabbingConstrained
-     */
-    constrain: function (elements) {
-      // Deactivate all tabbingContexts to prepare for the new constraint. A
-      // tabbingContext instance will only be reactivated if the stack is
-      // unwound to it in the _unwindStack() method.
+  $.extend(TabbingManager.prototype, {
+    constrain: function constrain(elements) {
       var il = this.stack.length;
       for (var i = 0; i < il; i++) {
         this.stack[i].deactivate();
       }
 
-      // The "active tabbing set" are the elements tabbing should be constrained
-      // to.
       var $elements = $(elements).find(':tabbable').addBack(':tabbable');
 
       var tabbingContext = new TabbingContext({
-        // The level is the current height of the stack before this new
-        // tabbingContext is pushed on top of the stack.
         level: this.stack.length,
         $tabbableElements: $elements
       });
 
       this.stack.push(tabbingContext);
 
-      // Activates the tabbingContext; this will manipulate the DOM to constrain
-      // tabbing.
       tabbingContext.activate();
 
-      // Allow modules to respond to the constrain event.
       $(document).trigger('drupalTabbingConstrained', tabbingContext);
 
       return tabbingContext;
     },
 
-    /**
-     * Restores a former tabbingContext when an active one is released.
-     *
-     * The TabbingManager stack of tabbingContext instances will be unwound
-     * from the top-most released tabbingContext down to the first non-released
-     * tabbingContext instance. This non-released instance is then activated.
-     */
-    release: function () {
-      // Unwind as far as possible: find the topmost non-released
-      // tabbingContext.
+    release: function release() {
       var toActivate = this.stack.length - 1;
       while (toActivate >= 0 && this.stack[toActivate].released) {
         toActivate--;
       }
 
-      // Delete all tabbingContexts after the to be activated one. They have
-      // already been deactivated, so their effect on the DOM has been reversed.
       this.stack.splice(toActivate + 1);
 
-      // Get topmost tabbingContext, if one exists, and activate it.
       if (toActivate >= 0) {
         this.stack[toActivate].activate();
       }
     },
 
-    /**
-     * Makes all elements outside of the tabbingContext's set untabbable.
-     *
-     * Elements made untabbable have their original tabindex and autofocus
-     * values stored so that they might be restored later when this
-     * tabbingContext is deactivated.
-     *
-     * @param {Drupal~TabbingContext} tabbingContext
-     *   The TabbingContext instance that has been activated.
-     */
-    activate: function (tabbingContext) {
+    activate: function activate(tabbingContext) {
       var $set = tabbingContext.$tabbableElements;
       var level = tabbingContext.level;
-      // Determine which elements are reachable via tabbing by default.
-      var $disabledSet = $(':tabbable')
-        // Exclude elements of the active tabbing set.
-        .not($set);
-      // Set the disabled set on the tabbingContext.
+
+      var $disabledSet = $(':tabbable').not($set);
+
       tabbingContext.$disabledElements = $disabledSet;
-      // Record the tabindex for each element, so we can restore it later.
+
       var il = $disabledSet.length;
       for (var i = 0; i < il; i++) {
         this.recordTabindex($disabledSet.eq(i), level);
       }
-      // Make all tabbable elements outside of the active tabbing set
-      // unreachable.
-      $disabledSet
-        .prop('tabindex', -1)
-        .prop('autofocus', false);
-
-      // Set focus on an element in the tabbingContext's set of tabbable
-      // elements. First, check if there is an element with an autofocus
-      // attribute. Select the last one from the DOM order.
+
+      $disabledSet.prop('tabindex', -1).prop('autofocus', false);
+
       var $hasFocus = $set.filter('[autofocus]').eq(-1);
-      // If no element in the tabbable set has an autofocus attribute, select
-      // the first element in the set.
+
       if ($hasFocus.length === 0) {
         $hasFocus = $set.eq(0);
       }
       $hasFocus.trigger('focus');
     },
 
-    /**
-     * Restores that tabbable state of a tabbingContext's disabled elements.
-     *
-     * Elements that were made untabbable have their original tabindex and
-     * autofocus values restored.
-     *
-     * @param {Drupal~TabbingContext} tabbingContext
-     *   The TabbingContext instance that has been deactivated.
-     */
-    deactivate: function (tabbingContext) {
+    deactivate: function deactivate(tabbingContext) {
       var $set = tabbingContext.$disabledElements;
       var level = tabbingContext.level;
       var il = $set.length;
@@ -187,15 +82,7 @@
       }
     },
 
-    /**
-     * Records the tabindex and autofocus values of an untabbable element.
-     *
-     * @param {jQuery} $el
-     *   The set of elements that have been disabled.
-     * @param {number} level
-     *   The stack level for which the tabindex attribute should be recorded.
-     */
-    recordTabindex: function ($el, level) {
+    recordTabindex: function recordTabindex($el, level) {
       var tabInfo = $el.data('drupalOriginalTabIndices') || {};
       tabInfo[level] = {
         tabindex: $el[0].getAttribute('tabindex'),
@@ -204,37 +91,22 @@
       $el.data('drupalOriginalTabIndices', tabInfo);
     },
 
-    /**
-     * Restores the tabindex and autofocus values of a reactivated element.
-     *
-     * @param {jQuery} $el
-     *   The element that is being reactivated.
-     * @param {number} level
-     *   The stack level for which the tabindex attribute should be restored.
-     */
-    restoreTabindex: function ($el, level) {
+    restoreTabindex: function restoreTabindex($el, level) {
       var tabInfo = $el.data('drupalOriginalTabIndices');
       if (tabInfo && tabInfo[level]) {
         var data = tabInfo[level];
         if (data.tabindex) {
           $el[0].setAttribute('tabindex', data.tabindex);
-        }
-        // If the element did not have a tabindex at this stack level then
-        // remove it.
-        else {
-          $el[0].removeAttribute('tabindex');
-        }
+        } else {
+            $el[0].removeAttribute('tabindex');
+          }
         if (data.autofocus) {
           $el[0].setAttribute('autofocus', 'autofocus');
         }
 
-        // Clean up $.data.
         if (level === 0) {
-          // Remove all data.
           $el.removeData('drupalOriginalTabIndices');
-        }
-        else {
-          // Remove the data for this stack level and higher.
+        } else {
           var levelToDelete = level;
           while (tabInfo.hasOwnProperty(levelToDelete)) {
             delete tabInfo[levelToDelete];
@@ -246,124 +118,54 @@
     }
   });
 
-  /**
-   * Stores a set of tabbable elements.
-   *
-   * This constraint can be removed with the release() method.
-   *
-   * @constructor Drupal~TabbingContext
-   *
-   * @param {object} options
-   *   A set of initiating values
-   * @param {number} options.level
-   *   The level in the TabbingManager's stack of this tabbingContext.
-   * @param {jQuery} options.$tabbableElements
-   *   The DOM elements that should be reachable via the tab key when this
-   *   tabbingContext is active.
-   * @param {jQuery} options.$disabledElements
-   *   The DOM elements that should not be reachable via the tab key when this
-   *   tabbingContext is active.
-   * @param {bool} options.released
-   *   A released tabbingContext can never be activated again. It will be
-   *   cleaned up when the TabbingManager unwinds its stack.
-   * @param {bool} options.active
-   *   When true, the tabbable elements of this tabbingContext will be reachable
-   *   via the tab key and the disabled elements will not. Only one
-   *   tabbingContext can be active at a time.
-   */
   function TabbingContext(options) {
 
-    $.extend(this, /** @lends Drupal~TabbingContext# */{
-
-      /**
-       * @type {?number}
-       */
+    $.extend(this, {
       level: null,
 
-      /**
-       * @type {jQuery}
-       */
       $tabbableElements: $(),
 
-      /**
-       * @type {jQuery}
-       */
       $disabledElements: $(),
 
-      /**
-       * @type {bool}
-       */
       released: false,
 
-      /**
-       * @type {bool}
-       */
       active: false
     }, options);
   }
 
-  /**
-   * Add public methods to the TabbingContext class.
-   */
-  $.extend(TabbingContext.prototype, /** @lends Drupal~TabbingContext# */{
-
-    /**
-     * Releases this TabbingContext.
-     *
-     * Once a TabbingContext object is released, it can never be activated
-     * again.
-     *
-     * @fires event:drupalTabbingContextReleased
-     */
-    release: function () {
+  $.extend(TabbingContext.prototype, {
+    release: function release() {
       if (!this.released) {
         this.deactivate();
         this.released = true;
         Drupal.tabbingManager.release(this);
-        // Allow modules to respond to the tabbingContext release event.
+
         $(document).trigger('drupalTabbingContextReleased', this);
       }
     },
 
-    /**
-     * Activates this TabbingContext.
-     *
-     * @fires event:drupalTabbingContextActivated
-     */
-    activate: function () {
-      // A released TabbingContext object can never be activated again.
+    activate: function activate() {
       if (!this.active && !this.released) {
         this.active = true;
         Drupal.tabbingManager.activate(this);
-        // Allow modules to respond to the constrain event.
+
         $(document).trigger('drupalTabbingContextActivated', this);
       }
     },
 
-    /**
-     * Deactivates this TabbingContext.
-     *
-     * @fires event:drupalTabbingContextDeactivated
-     */
-    deactivate: function () {
+    deactivate: function deactivate() {
       if (this.active) {
         this.active = false;
         Drupal.tabbingManager.deactivate(this);
-        // Allow modules to respond to the constrain event.
+
         $(document).trigger('drupalTabbingContextDeactivated', this);
       }
     }
   });
 
-  // Mark this behavior as processed on the first pass and return if it is
-  // already processed.
   if (Drupal.tabbingManager) {
     return;
   }
 
-  /**
-   * @type {Drupal~TabbingManager}
-   */
   Drupal.tabbingManager = new TabbingManager();
-
-}(jQuery, Drupal));
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/misc/tabledrag.es6.js b/core/misc/tabledrag.es6.js
new file mode 100644
index 000000000000..75468e60ddb4
--- /dev/null
+++ b/core/misc/tabledrag.es6.js
@@ -0,0 +1,1557 @@
+/**
+ * @file
+ * Provide dragging capabilities to admin uis.
+ */
+
+/**
+ * Triggers when weights columns are toggled.
+ *
+ * @event columnschange
+ */
+
+(function ($, Drupal, drupalSettings) {
+
+  'use strict';
+
+  /**
+   * Store the state of weight columns display for all tables.
+   *
+   * Default value is to hide weight columns.
+   */
+  var showWeight = JSON.parse(localStorage.getItem('Drupal.tableDrag.showWeight'));
+
+  /**
+   * Drag and drop table rows with field manipulation.
+   *
+   * Using the drupal_attach_tabledrag() function, any table with weights or
+   * parent relationships may be made into draggable tables. Columns containing
+   * a field may optionally be hidden, providing a better user experience.
+   *
+   * Created tableDrag instances may be modified with custom behaviors by
+   * overriding the .onDrag, .onDrop, .row.onSwap, and .row.onIndent methods.
+   * See blocks.js for an example of adding additional functionality to
+   * tableDrag.
+   *
+   * @type {Drupal~behavior}
+   */
+  Drupal.behaviors.tableDrag = {
+    attach: function (context, settings) {
+      function initTableDrag(table, base) {
+        if (table.length) {
+          // Create the new tableDrag instance. Save in the Drupal variable
+          // to allow other scripts access to the object.
+          Drupal.tableDrag[base] = new Drupal.tableDrag(table[0], settings.tableDrag[base]);
+        }
+      }
+
+      for (var base in settings.tableDrag) {
+        if (settings.tableDrag.hasOwnProperty(base)) {
+          initTableDrag($(context).find('#' + base).once('tabledrag'), base);
+        }
+      }
+    }
+  };
+
+  /**
+   * Provides table and field manipulation.
+   *
+   * @constructor
+   *
+   * @param {HTMLElement} table
+   *   DOM object for the table to be made draggable.
+   * @param {object} tableSettings
+   *   Settings for the table added via drupal_add_dragtable().
+   */
+  Drupal.tableDrag = function (table, tableSettings) {
+    var self = this;
+    var $table = $(table);
+
+    /**
+     * @type {jQuery}
+     */
+    this.$table = $(table);
+
+    /**
+     *
+     * @type {HTMLElement}
+     */
+    this.table = table;
+
+    /**
+     * @type {object}
+     */
+    this.tableSettings = tableSettings;
+
+    /**
+     * Used to hold information about a current drag operation.
+     *
+     * @type {?HTMLElement}
+     */
+    this.dragObject = null;
+
+    /**
+     * Provides operations for row manipulation.
+     *
+     * @type {?HTMLElement}
+     */
+    this.rowObject = null;
+
+    /**
+     * Remember the previous element.
+     *
+     * @type {?HTMLElement}
+     */
+    this.oldRowElement = null;
+
+    /**
+     * Used to determine up or down direction from last mouse move.
+     *
+     * @type {number}
+     */
+    this.oldY = 0;
+
+    /**
+     * Whether anything in the entire table has changed.
+     *
+     * @type {bool}
+     */
+    this.changed = false;
+
+    /**
+     * Maximum amount of allowed parenting.
+     *
+     * @type {number}
+     */
+    this.maxDepth = 0;
+
+    /**
+     * Direction of the table.
+     *
+     * @type {number}
+     */
+    this.rtl = $(this.table).css('direction') === 'rtl' ? -1 : 1;
+
+    /**
+     *
+     * @type {bool}
+     */
+    this.striping = $(this.table).data('striping') === 1;
+
+    /**
+     * Configure the scroll settings.
+     *
+     * @type {object}
+     *
+     * @prop {number} amount
+     * @prop {number} interval
+     * @prop {number} trigger
+     */
+    this.scrollSettings = {amount: 4, interval: 50, trigger: 70};
+
+    /**
+     *
+     * @type {?number}
+     */
+    this.scrollInterval = null;
+
+    /**
+     *
+     * @type {number}
+     */
+    this.scrollY = 0;
+
+    /**
+     *
+     * @type {number}
+     */
+    this.windowHeight = 0;
+
+    /**
+     * Check this table's settings for parent relationships.
+     *
+     * For efficiency, large sections of code can be skipped if we don't need to
+     * track horizontal movement and indentations.
+     *
+     * @type {bool}
+     */
+    this.indentEnabled = false;
+    for (var group in tableSettings) {
+      if (tableSettings.hasOwnProperty(group)) {
+        for (var n in tableSettings[group]) {
+          if (tableSettings[group].hasOwnProperty(n)) {
+            if (tableSettings[group][n].relationship === 'parent') {
+              this.indentEnabled = true;
+            }
+            if (tableSettings[group][n].limit > 0) {
+              this.maxDepth = tableSettings[group][n].limit;
+            }
+          }
+        }
+      }
+    }
+    if (this.indentEnabled) {
+
+      /**
+       * Total width of indents, set in makeDraggable.
+       *
+       * @type {number}
+       */
+      this.indentCount = 1;
+      // Find the width of indentations to measure mouse movements against.
+      // Because the table doesn't need to start with any indentations, we
+      // manually append 2 indentations in the first draggable row, measure
+      // the offset, then remove.
+      var indent = Drupal.theme('tableDragIndentation');
+      var testRow = $('<tr/>').addClass('draggable').appendTo(table);
+      var testCell = $('<td/>').appendTo(testRow).prepend(indent).prepend(indent);
+      var $indentation = testCell.find('.js-indentation');
+
+      /**
+       *
+       * @type {number}
+       */
+      this.indentAmount = $indentation.get(1).offsetLeft - $indentation.get(0).offsetLeft;
+      testRow.remove();
+    }
+
+    // Make each applicable row draggable.
+    // Match immediate children of the parent element to allow nesting.
+    $table.find('> tr.draggable, > tbody > tr.draggable').each(function () { self.makeDraggable(this); });
+
+    // Add a link before the table for users to show or hide weight columns.
+    $table.before($('<button type="button" class="link tabledrag-toggle-weight"></button>')
+      .attr('title', Drupal.t('Re-order rows by numerical weight instead of dragging.'))
+      .on('click', $.proxy(function (e) {
+        e.preventDefault();
+        this.toggleColumns();
+      }, this))
+      .wrap('<div class="tabledrag-toggle-weight-wrapper"></div>')
+      .parent()
+    );
+
+    // Initialize the specified columns (for example, weight or parent columns)
+    // to show or hide according to user preference. This aids accessibility
+    // so that, e.g., screen reader users can choose to enter weight values and
+    // manipulate form elements directly, rather than using drag-and-drop..
+    self.initColumns();
+
+    // Add event bindings to the document. The self variable is passed along
+    // as event handlers do not have direct access to the tableDrag object.
+    $(document).on('touchmove', function (event) { return self.dragRow(event.originalEvent.touches[0], self); });
+    $(document).on('touchend', function (event) { return self.dropRow(event.originalEvent.touches[0], self); });
+    $(document).on('mousemove pointermove', function (event) { return self.dragRow(event, self); });
+    $(document).on('mouseup pointerup', function (event) { return self.dropRow(event, self); });
+
+    // React to localStorage event showing or hiding weight columns.
+    $(window).on('storage', $.proxy(function (e) {
+      // Only react to 'Drupal.tableDrag.showWeight' value change.
+      if (e.originalEvent.key === 'Drupal.tableDrag.showWeight') {
+        // This was changed in another window, get the new value for this
+        // window.
+        showWeight = JSON.parse(e.originalEvent.newValue);
+        this.displayColumns(showWeight);
+      }
+    }, this));
+  };
+
+  /**
+   * Initialize columns containing form elements to be hidden by default.
+   *
+   * Identify and mark each cell with a CSS class so we can easily toggle
+   * show/hide it. Finally, hide columns if user does not have a
+   * 'Drupal.tableDrag.showWeight' localStorage value.
+   */
+  Drupal.tableDrag.prototype.initColumns = function () {
+    var $table = this.$table;
+    var hidden;
+    var cell;
+    var columnIndex;
+    for (var group in this.tableSettings) {
+      if (this.tableSettings.hasOwnProperty(group)) {
+
+        // Find the first field in this group.
+        for (var d in this.tableSettings[group]) {
+          if (this.tableSettings[group].hasOwnProperty(d)) {
+            var field = $table.find('.' + this.tableSettings[group][d].target).eq(0);
+            if (field.length && this.tableSettings[group][d].hidden) {
+              hidden = this.tableSettings[group][d].hidden;
+              cell = field.closest('td');
+              break;
+            }
+          }
+        }
+
+        // Mark the column containing this field so it can be hidden.
+        if (hidden && cell[0]) {
+          // Add 1 to our indexes. The nth-child selector is 1 based, not 0
+          // based. Match immediate children of the parent element to allow
+          // nesting.
+          columnIndex = cell.parent().find('> td').index(cell.get(0)) + 1;
+          $table.find('> thead > tr, > tbody > tr, > tr').each(this.addColspanClass(columnIndex));
+        }
+      }
+    }
+    this.displayColumns(showWeight);
+  };
+
+  /**
+   * Mark cells that have colspan.
+   *
+   * In order to adjust the colspan instead of hiding them altogether.
+   *
+   * @param {number} columnIndex
+   *   The column index to add colspan class to.
+   *
+   * @return {function}
+   *   Function to add colspan class.
+   */
+  Drupal.tableDrag.prototype.addColspanClass = function (columnIndex) {
+    return function () {
+      // Get the columnIndex and adjust for any colspans in this row.
+      var $row = $(this);
+      var index = columnIndex;
+      var cells = $row.children();
+      var cell;
+      cells.each(function (n) {
+        if (n < index && this.colSpan && this.colSpan > 1) {
+          index -= this.colSpan - 1;
+        }
+      });
+      if (index > 0) {
+        cell = cells.filter(':nth-child(' + index + ')');
+        if (cell[0].colSpan && cell[0].colSpan > 1) {
+          // If this cell has a colspan, mark it so we can reduce the colspan.
+          cell.addClass('tabledrag-has-colspan');
+        }
+        else {
+          // Mark this cell so we can hide it.
+          cell.addClass('tabledrag-hide');
+        }
+      }
+    };
+  };
+
+  /**
+   * Hide or display weight columns. Triggers an event on change.
+   *
+   * @fires event:columnschange
+   *
+   * @param {bool} displayWeight
+   *   'true' will show weight columns.
+   */
+  Drupal.tableDrag.prototype.displayColumns = function (displayWeight) {
+    if (displayWeight) {
+      this.showColumns();
+    }
+    // Default action is to hide columns.
+    else {
+      this.hideColumns();
+    }
+    // Trigger an event to allow other scripts to react to this display change.
+    // Force the extra parameter as a bool.
+    $('table').findOnce('tabledrag').trigger('columnschange', !!displayWeight);
+  };
+
+  /**
+   * Toggle the weight column depending on 'showWeight' value.
+   *
+   * Store only default override.
+   */
+  Drupal.tableDrag.prototype.toggleColumns = function () {
+    showWeight = !showWeight;
+    this.displayColumns(showWeight);
+    if (showWeight) {
+      // Save default override.
+      localStorage.setItem('Drupal.tableDrag.showWeight', showWeight);
+    }
+    else {
+      // Reset the value to its default.
+      localStorage.removeItem('Drupal.tableDrag.showWeight');
+    }
+  };
+
+  /**
+   * Hide the columns containing weight/parent form elements.
+   *
+   * Undo showColumns().
+   */
+  Drupal.tableDrag.prototype.hideColumns = function () {
+    var $tables = $('table').findOnce('tabledrag');
+    // Hide weight/parent cells and headers.
+    $tables.find('.tabledrag-hide').css('display', 'none');
+    // Show TableDrag handles.
+    $tables.find('.tabledrag-handle').css('display', '');
+    // Reduce the colspan of any effected multi-span columns.
+    $tables.find('.tabledrag-has-colspan').each(function () {
+      this.colSpan = this.colSpan - 1;
+    });
+    // Change link text.
+    $('.tabledrag-toggle-weight').text(Drupal.t('Show row weights'));
+  };
+
+  /**
+   * Show the columns containing weight/parent form elements.
+   *
+   * Undo hideColumns().
+   */
+  Drupal.tableDrag.prototype.showColumns = function () {
+    var $tables = $('table').findOnce('tabledrag');
+    // Show weight/parent cells and headers.
+    $tables.find('.tabledrag-hide').css('display', '');
+    // Hide TableDrag handles.
+    $tables.find('.tabledrag-handle').css('display', 'none');
+    // Increase the colspan for any columns where it was previously reduced.
+    $tables.find('.tabledrag-has-colspan').each(function () {
+      this.colSpan = this.colSpan + 1;
+    });
+    // Change link text.
+    $('.tabledrag-toggle-weight').text(Drupal.t('Hide row weights'));
+  };
+
+  /**
+   * Find the target used within a particular row and group.
+   *
+   * @param {string} group
+   *   Group selector.
+   * @param {HTMLElement} row
+   *   The row HTML element.
+   *
+   * @return {object}
+   *   The table row settings.
+   */
+  Drupal.tableDrag.prototype.rowSettings = function (group, row) {
+    var field = $(row).find('.' + group);
+    var tableSettingsGroup = this.tableSettings[group];
+    for (var delta in tableSettingsGroup) {
+      if (tableSettingsGroup.hasOwnProperty(delta)) {
+        var targetClass = tableSettingsGroup[delta].target;
+        if (field.is('.' + targetClass)) {
+          // Return a copy of the row settings.
+          var rowSettings = {};
+          for (var n in tableSettingsGroup[delta]) {
+            if (tableSettingsGroup[delta].hasOwnProperty(n)) {
+              rowSettings[n] = tableSettingsGroup[delta][n];
+            }
+          }
+          return rowSettings;
+        }
+      }
+    }
+  };
+
+  /**
+   * Take an item and add event handlers to make it become draggable.
+   *
+   * @param {HTMLElement} item
+   *   The item to add event handlers to.
+   */
+  Drupal.tableDrag.prototype.makeDraggable = function (item) {
+    var self = this;
+    var $item = $(item);
+    // Add a class to the title link.
+    $item.find('td:first-of-type').find('a').addClass('menu-item__link');
+    // Create the handle.
+    var handle = $('<a href="#" class="tabledrag-handle"><div class="handle">&nbsp;</div></a>').attr('title', Drupal.t('Drag to re-order'));
+    // Insert the handle after indentations (if any).
+    var $indentationLast = $item.find('td:first-of-type').find('.js-indentation').eq(-1);
+    if ($indentationLast.length) {
+      $indentationLast.after(handle);
+      // Update the total width of indentation in this entire table.
+      self.indentCount = Math.max($item.find('.js-indentation').length, self.indentCount);
+    }
+    else {
+      $item.find('td').eq(0).prepend(handle);
+    }
+
+    handle.on('mousedown touchstart pointerdown', function (event) {
+      event.preventDefault();
+      if (event.originalEvent.type === 'touchstart') {
+        event = event.originalEvent.touches[0];
+      }
+      self.dragStart(event, self, item);
+    });
+
+    // Prevent the anchor tag from jumping us to the top of the page.
+    handle.on('click', function (e) {
+      e.preventDefault();
+    });
+
+    // Set blur cleanup when a handle is focused.
+    handle.on('focus', function () {
+      self.safeBlur = true;
+    });
+
+    // On blur, fire the same function as a touchend/mouseup. This is used to
+    // update values after a row has been moved through the keyboard support.
+    handle.on('blur', function (event) {
+      if (self.rowObject && self.safeBlur) {
+        self.dropRow(event, self);
+      }
+    });
+
+    // Add arrow-key support to the handle.
+    handle.on('keydown', function (event) {
+      // If a rowObject doesn't yet exist and this isn't the tab key.
+      if (event.keyCode !== 9 && !self.rowObject) {
+        self.rowObject = new self.row(item, 'keyboard', self.indentEnabled, self.maxDepth, true);
+      }
+
+      var keyChange = false;
+      var groupHeight;
+
+      /* eslint-disable no-fallthrough */
+
+      switch (event.keyCode) {
+        // Left arrow.
+        case 37:
+        // Safari left arrow.
+        case 63234:
+          keyChange = true;
+          self.rowObject.indent(-1 * self.rtl);
+          break;
+
+        // Up arrow.
+        case 38:
+        // Safari up arrow.
+        case 63232:
+          var $previousRow = $(self.rowObject.element).prev('tr:first-of-type');
+          var previousRow = $previousRow.get(0);
+          while (previousRow && $previousRow.is(':hidden')) {
+            $previousRow = $(previousRow).prev('tr:first-of-type');
+            previousRow = $previousRow.get(0);
+          }
+          if (previousRow) {
+            // Do not allow the onBlur cleanup.
+            self.safeBlur = false;
+            self.rowObject.direction = 'up';
+            keyChange = true;
+
+            if ($(item).is('.tabledrag-root')) {
+              // Swap with the previous top-level row.
+              groupHeight = 0;
+              while (previousRow && $previousRow.find('.js-indentation').length) {
+                $previousRow = $(previousRow).prev('tr:first-of-type');
+                previousRow = $previousRow.get(0);
+                groupHeight += $previousRow.is(':hidden') ? 0 : previousRow.offsetHeight;
+              }
+              if (previousRow) {
+                self.rowObject.swap('before', previousRow);
+                // No need to check for indentation, 0 is the only valid one.
+                window.scrollBy(0, -groupHeight);
+              }
+            }
+            else if (self.table.tBodies[0].rows[0] !== previousRow || $previousRow.is('.draggable')) {
+              // Swap with the previous row (unless previous row is the first
+              // one and undraggable).
+              self.rowObject.swap('before', previousRow);
+              self.rowObject.interval = null;
+              self.rowObject.indent(0);
+              window.scrollBy(0, -parseInt(item.offsetHeight, 10));
+            }
+            // Regain focus after the DOM manipulation.
+            handle.trigger('focus');
+          }
+          break;
+
+        // Right arrow.
+        case 39:
+        // Safari right arrow.
+        case 63235:
+          keyChange = true;
+          self.rowObject.indent(self.rtl);
+          break;
+
+        // Down arrow.
+        case 40:
+        // Safari down arrow.
+        case 63233:
+          var $nextRow = $(self.rowObject.group).eq(-1).next('tr:first-of-type');
+          var nextRow = $nextRow.get(0);
+          while (nextRow && $nextRow.is(':hidden')) {
+            $nextRow = $(nextRow).next('tr:first-of-type');
+            nextRow = $nextRow.get(0);
+          }
+          if (nextRow) {
+            // Do not allow the onBlur cleanup.
+            self.safeBlur = false;
+            self.rowObject.direction = 'down';
+            keyChange = true;
+
+            if ($(item).is('.tabledrag-root')) {
+              // Swap with the next group (necessarily a top-level one).
+              groupHeight = 0;
+              var nextGroup = new self.row(nextRow, 'keyboard', self.indentEnabled, self.maxDepth, false);
+              if (nextGroup) {
+                $(nextGroup.group).each(function () {
+                  groupHeight += $(this).is(':hidden') ? 0 : this.offsetHeight;
+                });
+                var nextGroupRow = $(nextGroup.group).eq(-1).get(0);
+                self.rowObject.swap('after', nextGroupRow);
+                // No need to check for indentation, 0 is the only valid one.
+                window.scrollBy(0, parseInt(groupHeight, 10));
+              }
+            }
+            else {
+              // Swap with the next row.
+              self.rowObject.swap('after', nextRow);
+              self.rowObject.interval = null;
+              self.rowObject.indent(0);
+              window.scrollBy(0, parseInt(item.offsetHeight, 10));
+            }
+            // Regain focus after the DOM manipulation.
+            handle.trigger('focus');
+          }
+          break;
+      }
+
+      /* eslint-enable no-fallthrough */
+
+      if (self.rowObject && self.rowObject.changed === true) {
+        $(item).addClass('drag');
+        if (self.oldRowElement) {
+          $(self.oldRowElement).removeClass('drag-previous');
+        }
+        self.oldRowElement = item;
+        if (self.striping === true) {
+          self.restripeTable();
+        }
+        self.onDrag();
+      }
+
+      // Returning false if we have an arrow key to prevent scrolling.
+      if (keyChange) {
+        return false;
+      }
+    });
+
+    // Compatibility addition, return false on keypress to prevent unwanted
+    // scrolling. IE and Safari will suppress scrolling on keydown, but all
+    // other browsers need to return false on keypress.
+    // http://www.quirksmode.org/js/keys.html
+    handle.on('keypress', function (event) {
+
+      /* eslint-disable no-fallthrough */
+
+      switch (event.keyCode) {
+        // Left arrow.
+        case 37:
+        // Up arrow.
+        case 38:
+        // Right arrow.
+        case 39:
+        // Down arrow.
+        case 40:
+          return false;
+      }
+
+      /* eslint-enable no-fallthrough */
+
+    });
+  };
+
+  /**
+   * Pointer event initiator, creates drag object and information.
+   *
+   * @param {jQuery.Event} event
+   *   The event object that trigger the drag.
+   * @param {Drupal.tableDrag} self
+   *   The drag handle.
+   * @param {HTMLElement} item
+   *   The item that that is being dragged.
+   */
+  Drupal.tableDrag.prototype.dragStart = function (event, self, item) {
+    // Create a new dragObject recording the pointer information.
+    self.dragObject = {};
+    self.dragObject.initOffset = self.getPointerOffset(item, event);
+    self.dragObject.initPointerCoords = self.pointerCoords(event);
+    if (self.indentEnabled) {
+      self.dragObject.indentPointerPos = self.dragObject.initPointerCoords;
+    }
+
+    // If there's a lingering row object from the keyboard, remove its focus.
+    if (self.rowObject) {
+      $(self.rowObject.element).find('a.tabledrag-handle').trigger('blur');
+    }
+
+    // Create a new rowObject for manipulation of this row.
+    self.rowObject = new self.row(item, 'pointer', self.indentEnabled, self.maxDepth, true);
+
+    // Save the position of the table.
+    self.table.topY = $(self.table).offset().top;
+    self.table.bottomY = self.table.topY + self.table.offsetHeight;
+
+    // Add classes to the handle and row.
+    $(item).addClass('drag');
+
+    // Set the document to use the move cursor during drag.
+    $('body').addClass('drag');
+    if (self.oldRowElement) {
+      $(self.oldRowElement).removeClass('drag-previous');
+    }
+  };
+
+  /**
+   * Pointer movement handler, bound to document.
+   *
+   * @param {jQuery.Event} event
+   *   The pointer event.
+   * @param {Drupal.tableDrag} self
+   *   The tableDrag instance.
+   *
+   * @return {bool|undefined}
+   *   Undefined if no dragObject is defined, false otherwise.
+   */
+  Drupal.tableDrag.prototype.dragRow = function (event, self) {
+    if (self.dragObject) {
+      self.currentPointerCoords = self.pointerCoords(event);
+      var y = self.currentPointerCoords.y - self.dragObject.initOffset.y;
+      var x = self.currentPointerCoords.x - self.dragObject.initOffset.x;
+
+      // Check for row swapping and vertical scrolling.
+      if (y !== self.oldY) {
+        self.rowObject.direction = y > self.oldY ? 'down' : 'up';
+        // Update the old value.
+        self.oldY = y;
+        // Check if the window should be scrolled (and how fast).
+        var scrollAmount = self.checkScroll(self.currentPointerCoords.y);
+        // Stop any current scrolling.
+        clearInterval(self.scrollInterval);
+        // Continue scrolling if the mouse has moved in the scroll direction.
+        if (scrollAmount > 0 && self.rowObject.direction === 'down' || scrollAmount < 0 && self.rowObject.direction === 'up') {
+          self.setScroll(scrollAmount);
+        }
+
+        // If we have a valid target, perform the swap and restripe the table.
+        var currentRow = self.findDropTargetRow(x, y);
+        if (currentRow) {
+          if (self.rowObject.direction === 'down') {
+            self.rowObject.swap('after', currentRow, self);
+          }
+          else {
+            self.rowObject.swap('before', currentRow, self);
+          }
+          if (self.striping === true) {
+            self.restripeTable();
+          }
+        }
+      }
+
+      // Similar to row swapping, handle indentations.
+      if (self.indentEnabled) {
+        var xDiff = self.currentPointerCoords.x - self.dragObject.indentPointerPos.x;
+        // Set the number of indentations the pointer has been moved left or
+        // right.
+        var indentDiff = Math.round(xDiff / self.indentAmount);
+        // Indent the row with our estimated diff, which may be further
+        // restricted according to the rows around this row.
+        var indentChange = self.rowObject.indent(indentDiff);
+        // Update table and pointer indentations.
+        self.dragObject.indentPointerPos.x += self.indentAmount * indentChange * self.rtl;
+        self.indentCount = Math.max(self.indentCount, self.rowObject.indents);
+      }
+
+      return false;
+    }
+  };
+
+  /**
+   * Pointerup behavior.
+   *
+   * @param {jQuery.Event} event
+   *   The pointer event.
+   * @param {Drupal.tableDrag} self
+   *   The tableDrag instance.
+   */
+  Drupal.tableDrag.prototype.dropRow = function (event, self) {
+    var droppedRow;
+    var $droppedRow;
+
+    // Drop row functionality.
+    if (self.rowObject !== null) {
+      droppedRow = self.rowObject.element;
+      $droppedRow = $(droppedRow);
+      // The row is already in the right place so we just release it.
+      if (self.rowObject.changed === true) {
+        // Update the fields in the dropped row.
+        self.updateFields(droppedRow);
+
+        // If a setting exists for affecting the entire group, update all the
+        // fields in the entire dragged group.
+        for (var group in self.tableSettings) {
+          if (self.tableSettings.hasOwnProperty(group)) {
+            var rowSettings = self.rowSettings(group, droppedRow);
+            if (rowSettings.relationship === 'group') {
+              for (var n in self.rowObject.children) {
+                if (self.rowObject.children.hasOwnProperty(n)) {
+                  self.updateField(self.rowObject.children[n], group);
+                }
+              }
+            }
+          }
+        }
+
+        self.rowObject.markChanged();
+        if (self.changed === false) {
+          $(Drupal.theme('tableDragChangedWarning')).insertBefore(self.table).hide().fadeIn('slow');
+          self.changed = true;
+        }
+      }
+
+      if (self.indentEnabled) {
+        self.rowObject.removeIndentClasses();
+      }
+      if (self.oldRowElement) {
+        $(self.oldRowElement).removeClass('drag-previous');
+      }
+      $droppedRow.removeClass('drag').addClass('drag-previous');
+      self.oldRowElement = droppedRow;
+      self.onDrop();
+      self.rowObject = null;
+    }
+
+    // Functionality specific only to pointerup events.
+    if (self.dragObject !== null) {
+      self.dragObject = null;
+      $('body').removeClass('drag');
+      clearInterval(self.scrollInterval);
+    }
+  };
+
+  /**
+   * Get the coordinates from the event (allowing for browser differences).
+   *
+   * @param {jQuery.Event} event
+   *   The pointer event.
+   *
+   * @return {object}
+   *   An object with `x` and `y` keys indicating the position.
+   */
+  Drupal.tableDrag.prototype.pointerCoords = function (event) {
+    if (event.pageX || event.pageY) {
+      return {x: event.pageX, y: event.pageY};
+    }
+    return {
+      x: event.clientX + document.body.scrollLeft - document.body.clientLeft,
+      y: event.clientY + document.body.scrollTop - document.body.clientTop
+    };
+  };
+
+  /**
+   * Get the event offset from the target element.
+   *
+   * Given a target element and a pointer event, get the event offset from that
+   * element. To do this we need the element's position and the target position.
+   *
+   * @param {HTMLElement} target
+   *   The target HTML element.
+   * @param {jQuery.Event} event
+   *   The pointer event.
+   *
+   * @return {object}
+   *   An object with `x` and `y` keys indicating the position.
+   */
+  Drupal.tableDrag.prototype.getPointerOffset = function (target, event) {
+    var docPos = $(target).offset();
+    var pointerPos = this.pointerCoords(event);
+    return {x: pointerPos.x - docPos.left, y: pointerPos.y - docPos.top};
+  };
+
+  /**
+   * Find the row the mouse is currently over.
+   *
+   * This row is then taken and swapped with the one being dragged.
+   *
+   * @param {number} x
+   *   The x coordinate of the mouse on the page (not the screen).
+   * @param {number} y
+   *   The y coordinate of the mouse on the page (not the screen).
+   *
+   * @return {*}
+   *   The drop target row, if found.
+   */
+  Drupal.tableDrag.prototype.findDropTargetRow = function (x, y) {
+    var rows = $(this.table.tBodies[0].rows).not(':hidden');
+    for (var n = 0; n < rows.length; n++) {
+      var row = rows[n];
+      var $row = $(row);
+      var rowY = $row.offset().top;
+      var rowHeight;
+      // Because Safari does not report offsetHeight on table rows, but does on
+      // table cells, grab the firstChild of the row and use that instead.
+      // http://jacob.peargrove.com/blog/2006/technical/table-row-offsettop-bug-in-safari.
+      if (row.offsetHeight === 0) {
+        rowHeight = parseInt(row.firstChild.offsetHeight, 10) / 2;
+      }
+      // Other browsers.
+      else {
+        rowHeight = parseInt(row.offsetHeight, 10) / 2;
+      }
+
+      // Because we always insert before, we need to offset the height a bit.
+      if ((y > (rowY - rowHeight)) && (y < (rowY + rowHeight))) {
+        if (this.indentEnabled) {
+          // Check that this row is not a child of the row being dragged.
+          for (n in this.rowObject.group) {
+            if (this.rowObject.group[n] === row) {
+              return null;
+            }
+          }
+        }
+        else {
+          // Do not allow a row to be swapped with itself.
+          if (row === this.rowObject.element) {
+            return null;
+          }
+        }
+
+        // Check that swapping with this row is allowed.
+        if (!this.rowObject.isValidSwap(row)) {
+          return null;
+        }
+
+        // We may have found the row the mouse just passed over, but it doesn't
+        // take into account hidden rows. Skip backwards until we find a
+        // draggable row.
+        while ($row.is(':hidden') && $row.prev('tr').is(':hidden')) {
+          $row = $row.prev('tr:first-of-type');
+          row = $row.get(0);
+        }
+        return row;
+      }
+    }
+    return null;
+  };
+
+  /**
+   * After the row is dropped, update the table fields.
+   *
+   * @param {HTMLElement} changedRow
+   *   DOM object for the row that was just dropped.
+   */
+  Drupal.tableDrag.prototype.updateFields = function (changedRow) {
+    for (var group in this.tableSettings) {
+      if (this.tableSettings.hasOwnProperty(group)) {
+        // Each group may have a different setting for relationship, so we find
+        // the source rows for each separately.
+        this.updateField(changedRow, group);
+      }
+    }
+  };
+
+  /**
+   * After the row is dropped, update a single table field.
+   *
+   * @param {HTMLElement} changedRow
+   *   DOM object for the row that was just dropped.
+   * @param {string} group
+   *   The settings group on which field updates will occur.
+   */
+  Drupal.tableDrag.prototype.updateField = function (changedRow, group) {
+    var rowSettings = this.rowSettings(group, changedRow);
+    var $changedRow = $(changedRow);
+    var sourceRow;
+    var $previousRow;
+    var previousRow;
+    var useSibling;
+    // Set the row as its own target.
+    if (rowSettings.relationship === 'self' || rowSettings.relationship === 'group') {
+      sourceRow = changedRow;
+    }
+    // Siblings are easy, check previous and next rows.
+    else if (rowSettings.relationship === 'sibling') {
+      $previousRow = $changedRow.prev('tr:first-of-type');
+      previousRow = $previousRow.get(0);
+      var $nextRow = $changedRow.next('tr:first-of-type');
+      var nextRow = $nextRow.get(0);
+      sourceRow = changedRow;
+      if ($previousRow.is('.draggable') && $previousRow.find('.' + group).length) {
+        if (this.indentEnabled) {
+          if ($previousRow.find('.js-indentations').length === $changedRow.find('.js-indentations').length) {
+            sourceRow = previousRow;
+          }
+        }
+        else {
+          sourceRow = previousRow;
+        }
+      }
+      else if ($nextRow.is('.draggable') && $nextRow.find('.' + group).length) {
+        if (this.indentEnabled) {
+          if ($nextRow.find('.js-indentations').length === $changedRow.find('.js-indentations').length) {
+            sourceRow = nextRow;
+          }
+        }
+        else {
+          sourceRow = nextRow;
+        }
+      }
+    }
+    // Parents, look up the tree until we find a field not in this group.
+    // Go up as many parents as indentations in the changed row.
+    else if (rowSettings.relationship === 'parent') {
+      $previousRow = $changedRow.prev('tr');
+      previousRow = $previousRow;
+      while ($previousRow.length && $previousRow.find('.js-indentation').length >= this.rowObject.indents) {
+        $previousRow = $previousRow.prev('tr');
+        previousRow = $previousRow;
+      }
+      // If we found a row.
+      if ($previousRow.length) {
+        sourceRow = $previousRow.get(0);
+      }
+      // Otherwise we went all the way to the left of the table without finding
+      // a parent, meaning this item has been placed at the root level.
+      else {
+        // Use the first row in the table as source, because it's guaranteed to
+        // be at the root level. Find the first item, then compare this row
+        // against it as a sibling.
+        sourceRow = $(this.table).find('tr.draggable:first-of-type').get(0);
+        if (sourceRow === this.rowObject.element) {
+          sourceRow = $(this.rowObject.group[this.rowObject.group.length - 1]).next('tr.draggable').get(0);
+        }
+        useSibling = true;
+      }
+    }
+
+    // Because we may have moved the row from one category to another,
+    // take a look at our sibling and borrow its sources and targets.
+    this.copyDragClasses(sourceRow, changedRow, group);
+    rowSettings = this.rowSettings(group, changedRow);
+
+    // In the case that we're looking for a parent, but the row is at the top
+    // of the tree, copy our sibling's values.
+    if (useSibling) {
+      rowSettings.relationship = 'sibling';
+      rowSettings.source = rowSettings.target;
+    }
+
+    var targetClass = '.' + rowSettings.target;
+    var targetElement = $changedRow.find(targetClass).get(0);
+
+    // Check if a target element exists in this row.
+    if (targetElement) {
+      var sourceClass = '.' + rowSettings.source;
+      var sourceElement = $(sourceClass, sourceRow).get(0);
+      switch (rowSettings.action) {
+        case 'depth':
+          // Get the depth of the target row.
+          targetElement.value = $(sourceElement).closest('tr').find('.js-indentation').length;
+          break;
+
+        case 'match':
+          // Update the value.
+          targetElement.value = sourceElement.value;
+          break;
+
+        case 'order':
+          var siblings = this.rowObject.findSiblings(rowSettings);
+          if ($(targetElement).is('select')) {
+            // Get a list of acceptable values.
+            var values = [];
+            $(targetElement).find('option').each(function () {
+              values.push(this.value);
+            });
+            var maxVal = values[values.length - 1];
+            // Populate the values in the siblings.
+            $(siblings).find(targetClass).each(function () {
+              // If there are more items than possible values, assign the
+              // maximum value to the row.
+              if (values.length > 0) {
+                this.value = values.shift();
+              }
+              else {
+                this.value = maxVal;
+              }
+            });
+          }
+          else {
+            // Assume a numeric input field.
+            var weight = parseInt($(siblings[0]).find(targetClass).val(), 10) || 0;
+            $(siblings).find(targetClass).each(function () {
+              this.value = weight;
+              weight++;
+            });
+          }
+          break;
+      }
+    }
+  };
+
+  /**
+   * Copy all tableDrag related classes from one row to another.
+   *
+   * Copy all special tableDrag classes from one row's form elements to a
+   * different one, removing any special classes that the destination row
+   * may have had.
+   *
+   * @param {HTMLElement} sourceRow
+   *   The element for the source row.
+   * @param {HTMLElement} targetRow
+   *   The element for the target row.
+   * @param {string} group
+   *   The group selector.
+   */
+  Drupal.tableDrag.prototype.copyDragClasses = function (sourceRow, targetRow, group) {
+    var sourceElement = $(sourceRow).find('.' + group);
+    var targetElement = $(targetRow).find('.' + group);
+    if (sourceElement.length && targetElement.length) {
+      targetElement[0].className = sourceElement[0].className;
+    }
+  };
+
+  /**
+   * Check the suggested scroll of the table.
+   *
+   * @param {number} cursorY
+   *   The Y position of the cursor.
+   *
+   * @return {number}
+   *   The suggested scroll.
+   */
+  Drupal.tableDrag.prototype.checkScroll = function (cursorY) {
+    var de = document.documentElement;
+    var b = document.body;
+
+    var windowHeight = this.windowHeight = window.innerHeight || (de.clientHeight && de.clientWidth !== 0 ? de.clientHeight : b.offsetHeight);
+    var scrollY;
+    if (document.all) {
+      scrollY = this.scrollY = !de.scrollTop ? b.scrollTop : de.scrollTop;
+    }
+    else {
+      scrollY = this.scrollY = window.pageYOffset ? window.pageYOffset : window.scrollY;
+    }
+    var trigger = this.scrollSettings.trigger;
+    var delta = 0;
+
+    // Return a scroll speed relative to the edge of the screen.
+    if (cursorY - scrollY > windowHeight - trigger) {
+      delta = trigger / (windowHeight + scrollY - cursorY);
+      delta = (delta > 0 && delta < trigger) ? delta : trigger;
+      return delta * this.scrollSettings.amount;
+    }
+    else if (cursorY - scrollY < trigger) {
+      delta = trigger / (cursorY - scrollY);
+      delta = (delta > 0 && delta < trigger) ? delta : trigger;
+      return -delta * this.scrollSettings.amount;
+    }
+  };
+
+  /**
+   * Set the scroll for the table.
+   *
+   * @param {number} scrollAmount
+   *   The amount of scroll to apply to the window.
+   */
+  Drupal.tableDrag.prototype.setScroll = function (scrollAmount) {
+    var self = this;
+
+    this.scrollInterval = setInterval(function () {
+      // Update the scroll values stored in the object.
+      self.checkScroll(self.currentPointerCoords.y);
+      var aboveTable = self.scrollY > self.table.topY;
+      var belowTable = self.scrollY + self.windowHeight < self.table.bottomY;
+      if (scrollAmount > 0 && belowTable || scrollAmount < 0 && aboveTable) {
+        window.scrollBy(0, scrollAmount);
+      }
+    }, this.scrollSettings.interval);
+  };
+
+  /**
+   * Command to restripe table properly.
+   */
+  Drupal.tableDrag.prototype.restripeTable = function () {
+    // :even and :odd are reversed because jQuery counts from 0 and
+    // we count from 1, so we're out of sync.
+    // Match immediate children of the parent element to allow nesting.
+    $(this.table).find('> tbody > tr.draggable, > tr.draggable')
+      .filter(':visible')
+      .filter(':odd').removeClass('odd').addClass('even').end()
+      .filter(':even').removeClass('even').addClass('odd');
+  };
+
+  /**
+   * Stub function. Allows a custom handler when a row begins dragging.
+   *
+   * @return {null}
+   *   Returns null when the stub function is used.
+   */
+  Drupal.tableDrag.prototype.onDrag = function () {
+    return null;
+  };
+
+  /**
+   * Stub function. Allows a custom handler when a row is dropped.
+   *
+   * @return {null}
+   *   Returns null when the stub function is used.
+   */
+  Drupal.tableDrag.prototype.onDrop = function () {
+    return null;
+  };
+
+  /**
+   * Constructor to make a new object to manipulate a table row.
+   *
+   * @param {HTMLElement} tableRow
+   *   The DOM element for the table row we will be manipulating.
+   * @param {string} method
+   *   The method in which this row is being moved. Either 'keyboard' or
+   *   'mouse'.
+   * @param {bool} indentEnabled
+   *   Whether the containing table uses indentations. Used for optimizations.
+   * @param {number} maxDepth
+   *   The maximum amount of indentations this row may contain.
+   * @param {bool} addClasses
+   *   Whether we want to add classes to this row to indicate child
+   *   relationships.
+   */
+  Drupal.tableDrag.prototype.row = function (tableRow, method, indentEnabled, maxDepth, addClasses) {
+    var $tableRow = $(tableRow);
+
+    this.element = tableRow;
+    this.method = method;
+    this.group = [tableRow];
+    this.groupDepth = $tableRow.find('.js-indentation').length;
+    this.changed = false;
+    this.table = $tableRow.closest('table')[0];
+    this.indentEnabled = indentEnabled;
+    this.maxDepth = maxDepth;
+    // Direction the row is being moved.
+    this.direction = '';
+    if (this.indentEnabled) {
+      this.indents = $tableRow.find('.js-indentation').length;
+      this.children = this.findChildren(addClasses);
+      this.group = $.merge(this.group, this.children);
+      // Find the depth of this entire group.
+      for (var n = 0; n < this.group.length; n++) {
+        this.groupDepth = Math.max($(this.group[n]).find('.js-indentation').length, this.groupDepth);
+      }
+    }
+  };
+
+  /**
+   * Find all children of rowObject by indentation.
+   *
+   * @param {bool} addClasses
+   *   Whether we want to add classes to this row to indicate child
+   *   relationships.
+   *
+   * @return {Array}
+   *   An array of children of the row.
+   */
+  Drupal.tableDrag.prototype.row.prototype.findChildren = function (addClasses) {
+    var parentIndentation = this.indents;
+    var currentRow = $(this.element, this.table).next('tr.draggable');
+    var rows = [];
+    var child = 0;
+
+    function rowIndentation(indentNum, el) {
+      var self = $(el);
+      if (child === 1 && (indentNum === parentIndentation)) {
+        self.addClass('tree-child-first');
+      }
+      if (indentNum === parentIndentation) {
+        self.addClass('tree-child');
+      }
+      else if (indentNum > parentIndentation) {
+        self.addClass('tree-child-horizontal');
+      }
+    }
+
+    while (currentRow.length) {
+      // A greater indentation indicates this is a child.
+      if (currentRow.find('.js-indentation').length > parentIndentation) {
+        child++;
+        rows.push(currentRow[0]);
+        if (addClasses) {
+          currentRow.find('.js-indentation').each(rowIndentation);
+        }
+      }
+      else {
+        break;
+      }
+      currentRow = currentRow.next('tr.draggable');
+    }
+    if (addClasses && rows.length) {
+      $(rows[rows.length - 1]).find('.js-indentation:nth-child(' + (parentIndentation + 1) + ')').addClass('tree-child-last');
+    }
+    return rows;
+  };
+
+  /**
+   * Ensure that two rows are allowed to be swapped.
+   *
+   * @param {HTMLElement} row
+   *   DOM object for the row being considered for swapping.
+   *
+   * @return {bool}
+   *   Whether the swap is a valid swap or not.
+   */
+  Drupal.tableDrag.prototype.row.prototype.isValidSwap = function (row) {
+    var $row = $(row);
+    if (this.indentEnabled) {
+      var prevRow;
+      var nextRow;
+      if (this.direction === 'down') {
+        prevRow = row;
+        nextRow = $row.next('tr').get(0);
+      }
+      else {
+        prevRow = $row.prev('tr').get(0);
+        nextRow = row;
+      }
+      this.interval = this.validIndentInterval(prevRow, nextRow);
+
+      // We have an invalid swap if the valid indentations interval is empty.
+      if (this.interval.min > this.interval.max) {
+        return false;
+      }
+    }
+
+    // Do not let an un-draggable first row have anything put before it.
+    if (this.table.tBodies[0].rows[0] === row && $row.is(':not(.draggable)')) {
+      return false;
+    }
+
+    return true;
+  };
+
+  /**
+   * Perform the swap between two rows.
+   *
+   * @param {string} position
+   *   Whether the swap will occur 'before' or 'after' the given row.
+   * @param {HTMLElement} row
+   *   DOM element what will be swapped with the row group.
+   */
+  Drupal.tableDrag.prototype.row.prototype.swap = function (position, row) {
+    // Makes sure only DOM object are passed to Drupal.detachBehaviors().
+    this.group.forEach(function (row) {
+      Drupal.detachBehaviors(row, drupalSettings, 'move');
+    });
+    $(row)[position](this.group);
+    // Makes sure only DOM object are passed to Drupal.attachBehaviors()s.
+    this.group.forEach(function (row) {
+      Drupal.attachBehaviors(row, drupalSettings);
+    });
+    this.changed = true;
+    this.onSwap(row);
+  };
+
+  /**
+   * Determine the valid indentations interval for the row at a given position.
+   *
+   * @param {?HTMLElement} prevRow
+   *   DOM object for the row before the tested position
+   *   (or null for first position in the table).
+   * @param {?HTMLElement} nextRow
+   *   DOM object for the row after the tested position
+   *   (or null for last position in the table).
+   *
+   * @return {object}
+   *   An object with the keys `min` and `max` to indicate the valid indent
+   *   interval.
+   */
+  Drupal.tableDrag.prototype.row.prototype.validIndentInterval = function (prevRow, nextRow) {
+    var $prevRow = $(prevRow);
+    var minIndent;
+    var maxIndent;
+
+    // Minimum indentation:
+    // Do not orphan the next row.
+    minIndent = nextRow ? $(nextRow).find('.js-indentation').length : 0;
+
+    // Maximum indentation:
+    if (!prevRow || $prevRow.is(':not(.draggable)') || $(this.element).is('.tabledrag-root')) {
+      // Do not indent:
+      // - the first row in the table,
+      // - rows dragged below a non-draggable row,
+      // - 'root' rows.
+      maxIndent = 0;
+    }
+    else {
+      // Do not go deeper than as a child of the previous row.
+      maxIndent = $prevRow.find('.js-indentation').length + ($prevRow.is('.tabledrag-leaf') ? 0 : 1);
+      // Limit by the maximum allowed depth for the table.
+      if (this.maxDepth) {
+        maxIndent = Math.min(maxIndent, this.maxDepth - (this.groupDepth - this.indents));
+      }
+    }
+
+    return {min: minIndent, max: maxIndent};
+  };
+
+  /**
+   * Indent a row within the legal bounds of the table.
+   *
+   * @param {number} indentDiff
+   *   The number of additional indentations proposed for the row (can be
+   *   positive or negative). This number will be adjusted to nearest valid
+   *   indentation level for the row.
+   *
+   * @return {number}
+   *   The number of indentations applied.
+   */
+  Drupal.tableDrag.prototype.row.prototype.indent = function (indentDiff) {
+    var $group = $(this.group);
+    // Determine the valid indentations interval if not available yet.
+    if (!this.interval) {
+      var prevRow = $(this.element).prev('tr').get(0);
+      var nextRow = $group.eq(-1).next('tr').get(0);
+      this.interval = this.validIndentInterval(prevRow, nextRow);
+    }
+
+    // Adjust to the nearest valid indentation.
+    var indent = this.indents + indentDiff;
+    indent = Math.max(indent, this.interval.min);
+    indent = Math.min(indent, this.interval.max);
+    indentDiff = indent - this.indents;
+
+    for (var n = 1; n <= Math.abs(indentDiff); n++) {
+      // Add or remove indentations.
+      if (indentDiff < 0) {
+        $group.find('.js-indentation:first-of-type').remove();
+        this.indents--;
+      }
+      else {
+        $group.find('td:first-of-type').prepend(Drupal.theme('tableDragIndentation'));
+        this.indents++;
+      }
+    }
+    if (indentDiff) {
+      // Update indentation for this row.
+      this.changed = true;
+      this.groupDepth += indentDiff;
+      this.onIndent();
+    }
+
+    return indentDiff;
+  };
+
+  /**
+   * Find all siblings for a row.
+   *
+   * According to its subgroup or indentation. Note that the passed-in row is
+   * included in the list of siblings.
+   *
+   * @param {object} rowSettings
+   *   The field settings we're using to identify what constitutes a sibling.
+   *
+   * @return {Array}
+   *   An array of siblings.
+   */
+  Drupal.tableDrag.prototype.row.prototype.findSiblings = function (rowSettings) {
+    var siblings = [];
+    var directions = ['prev', 'next'];
+    var rowIndentation = this.indents;
+    var checkRowIndentation;
+    for (var d = 0; d < directions.length; d++) {
+      var checkRow = $(this.element)[directions[d]]();
+      while (checkRow.length) {
+        // Check that the sibling contains a similar target field.
+        if (checkRow.find('.' + rowSettings.target)) {
+          // Either add immediately if this is a flat table, or check to ensure
+          // that this row has the same level of indentation.
+          if (this.indentEnabled) {
+            checkRowIndentation = checkRow.find('.js-indentation').length;
+          }
+
+          if (!(this.indentEnabled) || (checkRowIndentation === rowIndentation)) {
+            siblings.push(checkRow[0]);
+          }
+          else if (checkRowIndentation < rowIndentation) {
+            // No need to keep looking for siblings when we get to a parent.
+            break;
+          }
+        }
+        else {
+          break;
+        }
+        checkRow = checkRow[directions[d]]();
+      }
+      // Since siblings are added in reverse order for previous, reverse the
+      // completed list of previous siblings. Add the current row and continue.
+      if (directions[d] === 'prev') {
+        siblings.reverse();
+        siblings.push(this.element);
+      }
+    }
+    return siblings;
+  };
+
+  /**
+   * Remove indentation helper classes from the current row group.
+   */
+  Drupal.tableDrag.prototype.row.prototype.removeIndentClasses = function () {
+    for (var n in this.children) {
+      if (this.children.hasOwnProperty(n)) {
+        $(this.children[n]).find('.js-indentation')
+          .removeClass('tree-child')
+          .removeClass('tree-child-first')
+          .removeClass('tree-child-last')
+          .removeClass('tree-child-horizontal');
+      }
+    }
+  };
+
+  /**
+   * Add an asterisk or other marker to the changed row.
+   */
+  Drupal.tableDrag.prototype.row.prototype.markChanged = function () {
+    var marker = Drupal.theme('tableDragChangedMarker');
+    var cell = $(this.element).find('td:first-of-type');
+    if (cell.find('abbr.tabledrag-changed').length === 0) {
+      cell.append(marker);
+    }
+  };
+
+  /**
+   * Stub function. Allows a custom handler when a row is indented.
+   *
+   * @return {null}
+   *   Returns null when the stub function is used.
+   */
+  Drupal.tableDrag.prototype.row.prototype.onIndent = function () {
+    return null;
+  };
+
+  /**
+   * Stub function. Allows a custom handler when a row is swapped.
+   *
+   * @param {HTMLElement} swappedRow
+   *   The element for the swapped row.
+   *
+   * @return {null}
+   *   Returns null when the stub function is used.
+   */
+  Drupal.tableDrag.prototype.row.prototype.onSwap = function (swappedRow) {
+    return null;
+  };
+
+  $.extend(Drupal.theme, /** @lends Drupal.theme */{
+
+    /**
+     * @return {string}
+     *  Markup for the marker.
+     */
+    tableDragChangedMarker: function () {
+      return '<abbr class="warning tabledrag-changed" title="' + Drupal.t('Changed') + '">*</abbr>';
+    },
+
+    /**
+     * @return {string}
+     *   Markup for the indentation.
+     */
+    tableDragIndentation: function () {
+      return '<div class="js-indentation indentation">&nbsp;</div>';
+    },
+
+    /**
+     * @return {string}
+     *   Markup for the warning.
+     */
+    tableDragChangedWarning: function () {
+      return '<div class="tabledrag-changed-warning messages messages--warning" role="alert">' + Drupal.theme('tableDragChangedMarker') + ' ' + Drupal.t('You have unsaved changes.') + '</div>';
+    }
+  });
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/misc/tabledrag.js b/core/misc/tabledrag.js
index 75468e60ddb4..550c7a6bc88d 100644
--- a/core/misc/tabledrag.js
+++ b/core/misc/tabledrag.js
@@ -1,45 +1,21 @@
 /**
- * @file
- * Provide dragging capabilities to admin uis.
- */
-
-/**
- * Triggers when weights columns are toggled.
- *
- * @event columnschange
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./misc/tabledrag.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings) {
 
   'use strict';
 
-  /**
-   * Store the state of weight columns display for all tables.
-   *
-   * Default value is to hide weight columns.
-   */
   var showWeight = JSON.parse(localStorage.getItem('Drupal.tableDrag.showWeight'));
 
-  /**
-   * Drag and drop table rows with field manipulation.
-   *
-   * Using the drupal_attach_tabledrag() function, any table with weights or
-   * parent relationships may be made into draggable tables. Columns containing
-   * a field may optionally be hidden, providing a better user experience.
-   *
-   * Created tableDrag instances may be modified with custom behaviors by
-   * overriding the .onDrag, .onDrop, .row.onSwap, and .row.onIndent methods.
-   * See blocks.js for an example of adding additional functionality to
-   * tableDrag.
-   *
-   * @type {Drupal~behavior}
-   */
   Drupal.behaviors.tableDrag = {
-    attach: function (context, settings) {
+    attach: function attach(context, settings) {
       function initTableDrag(table, base) {
         if (table.length) {
-          // Create the new tableDrag instance. Save in the Drupal variable
-          // to allow other scripts access to the object.
           Drupal.tableDrag[base] = new Drupal.tableDrag(table[0], settings.tableDrag[base]);
         }
       }
@@ -52,128 +28,40 @@
     }
   };
 
-  /**
-   * Provides table and field manipulation.
-   *
-   * @constructor
-   *
-   * @param {HTMLElement} table
-   *   DOM object for the table to be made draggable.
-   * @param {object} tableSettings
-   *   Settings for the table added via drupal_add_dragtable().
-   */
   Drupal.tableDrag = function (table, tableSettings) {
     var self = this;
     var $table = $(table);
 
-    /**
-     * @type {jQuery}
-     */
     this.$table = $(table);
 
-    /**
-     *
-     * @type {HTMLElement}
-     */
     this.table = table;
 
-    /**
-     * @type {object}
-     */
     this.tableSettings = tableSettings;
 
-    /**
-     * Used to hold information about a current drag operation.
-     *
-     * @type {?HTMLElement}
-     */
     this.dragObject = null;
 
-    /**
-     * Provides operations for row manipulation.
-     *
-     * @type {?HTMLElement}
-     */
     this.rowObject = null;
 
-    /**
-     * Remember the previous element.
-     *
-     * @type {?HTMLElement}
-     */
     this.oldRowElement = null;
 
-    /**
-     * Used to determine up or down direction from last mouse move.
-     *
-     * @type {number}
-     */
     this.oldY = 0;
 
-    /**
-     * Whether anything in the entire table has changed.
-     *
-     * @type {bool}
-     */
     this.changed = false;
 
-    /**
-     * Maximum amount of allowed parenting.
-     *
-     * @type {number}
-     */
     this.maxDepth = 0;
 
-    /**
-     * Direction of the table.
-     *
-     * @type {number}
-     */
     this.rtl = $(this.table).css('direction') === 'rtl' ? -1 : 1;
 
-    /**
-     *
-     * @type {bool}
-     */
     this.striping = $(this.table).data('striping') === 1;
 
-    /**
-     * Configure the scroll settings.
-     *
-     * @type {object}
-     *
-     * @prop {number} amount
-     * @prop {number} interval
-     * @prop {number} trigger
-     */
-    this.scrollSettings = {amount: 4, interval: 50, trigger: 70};
-
-    /**
-     *
-     * @type {?number}
-     */
+    this.scrollSettings = { amount: 4, interval: 50, trigger: 70 };
+
     this.scrollInterval = null;
 
-    /**
-     *
-     * @type {number}
-     */
     this.scrollY = 0;
 
-    /**
-     *
-     * @type {number}
-     */
     this.windowHeight = 0;
 
-    /**
-     * Check this table's settings for parent relationships.
-     *
-     * For efficiency, large sections of code can be skipped if we don't need to
-     * track horizontal movement and indentations.
-     *
-     * @type {bool}
-     */
     this.indentEnabled = false;
     for (var group in tableSettings) {
       if (tableSettings.hasOwnProperty(group)) {
@@ -190,77 +78,49 @@
       }
     }
     if (this.indentEnabled) {
-
-      /**
-       * Total width of indents, set in makeDraggable.
-       *
-       * @type {number}
-       */
       this.indentCount = 1;
-      // Find the width of indentations to measure mouse movements against.
-      // Because the table doesn't need to start with any indentations, we
-      // manually append 2 indentations in the first draggable row, measure
-      // the offset, then remove.
+
       var indent = Drupal.theme('tableDragIndentation');
       var testRow = $('<tr/>').addClass('draggable').appendTo(table);
       var testCell = $('<td/>').appendTo(testRow).prepend(indent).prepend(indent);
       var $indentation = testCell.find('.js-indentation');
 
-      /**
-       *
-       * @type {number}
-       */
       this.indentAmount = $indentation.get(1).offsetLeft - $indentation.get(0).offsetLeft;
       testRow.remove();
     }
 
-    // Make each applicable row draggable.
-    // Match immediate children of the parent element to allow nesting.
-    $table.find('> tr.draggable, > tbody > tr.draggable').each(function () { self.makeDraggable(this); });
-
-    // Add a link before the table for users to show or hide weight columns.
-    $table.before($('<button type="button" class="link tabledrag-toggle-weight"></button>')
-      .attr('title', Drupal.t('Re-order rows by numerical weight instead of dragging.'))
-      .on('click', $.proxy(function (e) {
-        e.preventDefault();
-        this.toggleColumns();
-      }, this))
-      .wrap('<div class="tabledrag-toggle-weight-wrapper"></div>')
-      .parent()
-    );
-
-    // Initialize the specified columns (for example, weight or parent columns)
-    // to show or hide according to user preference. This aids accessibility
-    // so that, e.g., screen reader users can choose to enter weight values and
-    // manipulate form elements directly, rather than using drag-and-drop..
+    $table.find('> tr.draggable, > tbody > tr.draggable').each(function () {
+      self.makeDraggable(this);
+    });
+
+    $table.before($('<button type="button" class="link tabledrag-toggle-weight"></button>').attr('title', Drupal.t('Re-order rows by numerical weight instead of dragging.')).on('click', $.proxy(function (e) {
+      e.preventDefault();
+      this.toggleColumns();
+    }, this)).wrap('<div class="tabledrag-toggle-weight-wrapper"></div>').parent());
+
     self.initColumns();
 
-    // Add event bindings to the document. The self variable is passed along
-    // as event handlers do not have direct access to the tableDrag object.
-    $(document).on('touchmove', function (event) { return self.dragRow(event.originalEvent.touches[0], self); });
-    $(document).on('touchend', function (event) { return self.dropRow(event.originalEvent.touches[0], self); });
-    $(document).on('mousemove pointermove', function (event) { return self.dragRow(event, self); });
-    $(document).on('mouseup pointerup', function (event) { return self.dropRow(event, self); });
+    $(document).on('touchmove', function (event) {
+      return self.dragRow(event.originalEvent.touches[0], self);
+    });
+    $(document).on('touchend', function (event) {
+      return self.dropRow(event.originalEvent.touches[0], self);
+    });
+    $(document).on('mousemove pointermove', function (event) {
+      return self.dragRow(event, self);
+    });
+    $(document).on('mouseup pointerup', function (event) {
+      return self.dropRow(event, self);
+    });
 
-    // React to localStorage event showing or hiding weight columns.
     $(window).on('storage', $.proxy(function (e) {
-      // Only react to 'Drupal.tableDrag.showWeight' value change.
       if (e.originalEvent.key === 'Drupal.tableDrag.showWeight') {
-        // This was changed in another window, get the new value for this
-        // window.
         showWeight = JSON.parse(e.originalEvent.newValue);
         this.displayColumns(showWeight);
       }
     }, this));
   };
 
-  /**
-   * Initialize columns containing form elements to be hidden by default.
-   *
-   * Identify and mark each cell with a CSS class so we can easily toggle
-   * show/hide it. Finally, hide columns if user does not have a
-   * 'Drupal.tableDrag.showWeight' localStorage value.
-   */
   Drupal.tableDrag.prototype.initColumns = function () {
     var $table = this.$table;
     var hidden;
@@ -268,8 +128,6 @@
     var columnIndex;
     for (var group in this.tableSettings) {
       if (this.tableSettings.hasOwnProperty(group)) {
-
-        // Find the first field in this group.
         for (var d in this.tableSettings[group]) {
           if (this.tableSettings[group].hasOwnProperty(d)) {
             var field = $table.find('.' + this.tableSettings[group][d].target).eq(0);
@@ -281,11 +139,7 @@
           }
         }
 
-        // Mark the column containing this field so it can be hidden.
         if (hidden && cell[0]) {
-          // Add 1 to our indexes. The nth-child selector is 1 based, not 0
-          // based. Match immediate children of the parent element to allow
-          // nesting.
           columnIndex = cell.parent().find('> td').index(cell.get(0)) + 1;
           $table.find('> thead > tr, > tbody > tr, > tr').each(this.addColspanClass(columnIndex));
         }
@@ -294,20 +148,8 @@
     this.displayColumns(showWeight);
   };
 
-  /**
-   * Mark cells that have colspan.
-   *
-   * In order to adjust the colspan instead of hiding them altogether.
-   *
-   * @param {number} columnIndex
-   *   The column index to add colspan class to.
-   *
-   * @return {function}
-   *   Function to add colspan class.
-   */
   Drupal.tableDrag.prototype.addColspanClass = function (columnIndex) {
     return function () {
-      // Get the columnIndex and adjust for any colspans in this row.
       var $row = $(this);
       var index = columnIndex;
       var cells = $row.children();
@@ -320,105 +162,62 @@
       if (index > 0) {
         cell = cells.filter(':nth-child(' + index + ')');
         if (cell[0].colSpan && cell[0].colSpan > 1) {
-          // If this cell has a colspan, mark it so we can reduce the colspan.
           cell.addClass('tabledrag-has-colspan');
-        }
-        else {
-          // Mark this cell so we can hide it.
+        } else {
           cell.addClass('tabledrag-hide');
         }
       }
     };
   };
 
-  /**
-   * Hide or display weight columns. Triggers an event on change.
-   *
-   * @fires event:columnschange
-   *
-   * @param {bool} displayWeight
-   *   'true' will show weight columns.
-   */
   Drupal.tableDrag.prototype.displayColumns = function (displayWeight) {
     if (displayWeight) {
       this.showColumns();
-    }
-    // Default action is to hide columns.
-    else {
-      this.hideColumns();
-    }
-    // Trigger an event to allow other scripts to react to this display change.
-    // Force the extra parameter as a bool.
+    } else {
+        this.hideColumns();
+      }
+
     $('table').findOnce('tabledrag').trigger('columnschange', !!displayWeight);
   };
 
-  /**
-   * Toggle the weight column depending on 'showWeight' value.
-   *
-   * Store only default override.
-   */
   Drupal.tableDrag.prototype.toggleColumns = function () {
     showWeight = !showWeight;
     this.displayColumns(showWeight);
     if (showWeight) {
-      // Save default override.
       localStorage.setItem('Drupal.tableDrag.showWeight', showWeight);
-    }
-    else {
-      // Reset the value to its default.
+    } else {
       localStorage.removeItem('Drupal.tableDrag.showWeight');
     }
   };
 
-  /**
-   * Hide the columns containing weight/parent form elements.
-   *
-   * Undo showColumns().
-   */
   Drupal.tableDrag.prototype.hideColumns = function () {
     var $tables = $('table').findOnce('tabledrag');
-    // Hide weight/parent cells and headers.
+
     $tables.find('.tabledrag-hide').css('display', 'none');
-    // Show TableDrag handles.
+
     $tables.find('.tabledrag-handle').css('display', '');
-    // Reduce the colspan of any effected multi-span columns.
+
     $tables.find('.tabledrag-has-colspan').each(function () {
       this.colSpan = this.colSpan - 1;
     });
-    // Change link text.
+
     $('.tabledrag-toggle-weight').text(Drupal.t('Show row weights'));
   };
 
-  /**
-   * Show the columns containing weight/parent form elements.
-   *
-   * Undo hideColumns().
-   */
   Drupal.tableDrag.prototype.showColumns = function () {
     var $tables = $('table').findOnce('tabledrag');
-    // Show weight/parent cells and headers.
+
     $tables.find('.tabledrag-hide').css('display', '');
-    // Hide TableDrag handles.
+
     $tables.find('.tabledrag-handle').css('display', 'none');
-    // Increase the colspan for any columns where it was previously reduced.
+
     $tables.find('.tabledrag-has-colspan').each(function () {
       this.colSpan = this.colSpan + 1;
     });
-    // Change link text.
+
     $('.tabledrag-toggle-weight').text(Drupal.t('Hide row weights'));
   };
 
-  /**
-   * Find the target used within a particular row and group.
-   *
-   * @param {string} group
-   *   Group selector.
-   * @param {HTMLElement} row
-   *   The row HTML element.
-   *
-   * @return {object}
-   *   The table row settings.
-   */
   Drupal.tableDrag.prototype.rowSettings = function (group, row) {
     var field = $(row).find('.' + group);
     var tableSettingsGroup = this.tableSettings[group];
@@ -426,7 +225,6 @@
       if (tableSettingsGroup.hasOwnProperty(delta)) {
         var targetClass = tableSettingsGroup[delta].target;
         if (field.is('.' + targetClass)) {
-          // Return a copy of the row settings.
           var rowSettings = {};
           for (var n in tableSettingsGroup[delta]) {
             if (tableSettingsGroup[delta].hasOwnProperty(n)) {
@@ -439,27 +237,20 @@
     }
   };
 
-  /**
-   * Take an item and add event handlers to make it become draggable.
-   *
-   * @param {HTMLElement} item
-   *   The item to add event handlers to.
-   */
   Drupal.tableDrag.prototype.makeDraggable = function (item) {
     var self = this;
     var $item = $(item);
-    // Add a class to the title link.
+
     $item.find('td:first-of-type').find('a').addClass('menu-item__link');
-    // Create the handle.
+
     var handle = $('<a href="#" class="tabledrag-handle"><div class="handle">&nbsp;</div></a>').attr('title', Drupal.t('Drag to re-order'));
-    // Insert the handle after indentations (if any).
+
     var $indentationLast = $item.find('td:first-of-type').find('.js-indentation').eq(-1);
     if ($indentationLast.length) {
       $indentationLast.after(handle);
-      // Update the total width of indentation in this entire table.
+
       self.indentCount = Math.max($item.find('.js-indentation').length, self.indentCount);
-    }
-    else {
+    } else {
       $item.find('td').eq(0).prepend(handle);
     }
 
@@ -471,27 +262,21 @@
       self.dragStart(event, self, item);
     });
 
-    // Prevent the anchor tag from jumping us to the top of the page.
     handle.on('click', function (e) {
       e.preventDefault();
     });
 
-    // Set blur cleanup when a handle is focused.
     handle.on('focus', function () {
       self.safeBlur = true;
     });
 
-    // On blur, fire the same function as a touchend/mouseup. This is used to
-    // update values after a row has been moved through the keyboard support.
     handle.on('blur', function (event) {
       if (self.rowObject && self.safeBlur) {
         self.dropRow(event, self);
       }
     });
 
-    // Add arrow-key support to the handle.
     handle.on('keydown', function (event) {
-      // If a rowObject doesn't yet exist and this isn't the tab key.
       if (event.keyCode !== 9 && !self.rowObject) {
         self.rowObject = new self.row(item, 'keyboard', self.indentEnabled, self.maxDepth, true);
       }
@@ -499,20 +284,14 @@
       var keyChange = false;
       var groupHeight;
 
-      /* eslint-disable no-fallthrough */
-
       switch (event.keyCode) {
-        // Left arrow.
         case 37:
-        // Safari left arrow.
         case 63234:
           keyChange = true;
           self.rowObject.indent(-1 * self.rtl);
           break;
 
-        // Up arrow.
         case 38:
-        // Safari up arrow.
         case 63232:
           var $previousRow = $(self.rowObject.element).prev('tr:first-of-type');
           var previousRow = $previousRow.get(0);
@@ -521,13 +300,11 @@
             previousRow = $previousRow.get(0);
           }
           if (previousRow) {
-            // Do not allow the onBlur cleanup.
             self.safeBlur = false;
             self.rowObject.direction = 'up';
             keyChange = true;
 
             if ($(item).is('.tabledrag-root')) {
-              // Swap with the previous top-level row.
               groupHeight = 0;
               while (previousRow && $previousRow.find('.js-indentation').length) {
                 $previousRow = $(previousRow).prev('tr:first-of-type');
@@ -536,34 +313,27 @@
               }
               if (previousRow) {
                 self.rowObject.swap('before', previousRow);
-                // No need to check for indentation, 0 is the only valid one.
+
                 window.scrollBy(0, -groupHeight);
               }
-            }
-            else if (self.table.tBodies[0].rows[0] !== previousRow || $previousRow.is('.draggable')) {
-              // Swap with the previous row (unless previous row is the first
-              // one and undraggable).
+            } else if (self.table.tBodies[0].rows[0] !== previousRow || $previousRow.is('.draggable')) {
               self.rowObject.swap('before', previousRow);
               self.rowObject.interval = null;
               self.rowObject.indent(0);
               window.scrollBy(0, -parseInt(item.offsetHeight, 10));
             }
-            // Regain focus after the DOM manipulation.
+
             handle.trigger('focus');
           }
           break;
 
-        // Right arrow.
         case 39:
-        // Safari right arrow.
         case 63235:
           keyChange = true;
           self.rowObject.indent(self.rtl);
           break;
 
-        // Down arrow.
         case 40:
-        // Safari down arrow.
         case 63233:
           var $nextRow = $(self.rowObject.group).eq(-1).next('tr:first-of-type');
           var nextRow = $nextRow.get(0);
@@ -572,13 +342,11 @@
             nextRow = $nextRow.get(0);
           }
           if (nextRow) {
-            // Do not allow the onBlur cleanup.
             self.safeBlur = false;
             self.rowObject.direction = 'down';
             keyChange = true;
 
             if ($(item).is('.tabledrag-root')) {
-              // Swap with the next group (necessarily a top-level one).
               groupHeight = 0;
               var nextGroup = new self.row(nextRow, 'keyboard', self.indentEnabled, self.maxDepth, false);
               if (nextGroup) {
@@ -587,25 +355,21 @@
                 });
                 var nextGroupRow = $(nextGroup.group).eq(-1).get(0);
                 self.rowObject.swap('after', nextGroupRow);
-                // No need to check for indentation, 0 is the only valid one.
+
                 window.scrollBy(0, parseInt(groupHeight, 10));
               }
-            }
-            else {
-              // Swap with the next row.
+            } else {
               self.rowObject.swap('after', nextRow);
               self.rowObject.interval = null;
               self.rowObject.indent(0);
               window.scrollBy(0, parseInt(item.offsetHeight, 10));
             }
-            // Regain focus after the DOM manipulation.
+
             handle.trigger('focus');
           }
           break;
       }
 
-      /* eslint-enable no-fallthrough */
-
       if (self.rowObject && self.rowObject.changed === true) {
         $(item).addClass('drag');
         if (self.oldRowElement) {
@@ -618,49 +382,24 @@
         self.onDrag();
       }
 
-      // Returning false if we have an arrow key to prevent scrolling.
       if (keyChange) {
         return false;
       }
     });
 
-    // Compatibility addition, return false on keypress to prevent unwanted
-    // scrolling. IE and Safari will suppress scrolling on keydown, but all
-    // other browsers need to return false on keypress.
-    // http://www.quirksmode.org/js/keys.html
     handle.on('keypress', function (event) {
 
-      /* eslint-disable no-fallthrough */
-
       switch (event.keyCode) {
-        // Left arrow.
         case 37:
-        // Up arrow.
         case 38:
-        // Right arrow.
         case 39:
-        // Down arrow.
         case 40:
           return false;
       }
-
-      /* eslint-enable no-fallthrough */
-
     });
   };
 
-  /**
-   * Pointer event initiator, creates drag object and information.
-   *
-   * @param {jQuery.Event} event
-   *   The event object that trigger the drag.
-   * @param {Drupal.tableDrag} self
-   *   The drag handle.
-   * @param {HTMLElement} item
-   *   The item that that is being dragged.
-   */
   Drupal.tableDrag.prototype.dragStart = function (event, self, item) {
-    // Create a new dragObject recording the pointer information.
     self.dragObject = {};
     self.dragObject.initOffset = self.getPointerOffset(item, event);
     self.dragObject.initPointerCoords = self.pointerCoords(event);
@@ -668,66 +407,47 @@
       self.dragObject.indentPointerPos = self.dragObject.initPointerCoords;
     }
 
-    // If there's a lingering row object from the keyboard, remove its focus.
     if (self.rowObject) {
       $(self.rowObject.element).find('a.tabledrag-handle').trigger('blur');
     }
 
-    // Create a new rowObject for manipulation of this row.
     self.rowObject = new self.row(item, 'pointer', self.indentEnabled, self.maxDepth, true);
 
-    // Save the position of the table.
     self.table.topY = $(self.table).offset().top;
     self.table.bottomY = self.table.topY + self.table.offsetHeight;
 
-    // Add classes to the handle and row.
     $(item).addClass('drag');
 
-    // Set the document to use the move cursor during drag.
     $('body').addClass('drag');
     if (self.oldRowElement) {
       $(self.oldRowElement).removeClass('drag-previous');
     }
   };
 
-  /**
-   * Pointer movement handler, bound to document.
-   *
-   * @param {jQuery.Event} event
-   *   The pointer event.
-   * @param {Drupal.tableDrag} self
-   *   The tableDrag instance.
-   *
-   * @return {bool|undefined}
-   *   Undefined if no dragObject is defined, false otherwise.
-   */
   Drupal.tableDrag.prototype.dragRow = function (event, self) {
     if (self.dragObject) {
       self.currentPointerCoords = self.pointerCoords(event);
       var y = self.currentPointerCoords.y - self.dragObject.initOffset.y;
       var x = self.currentPointerCoords.x - self.dragObject.initOffset.x;
 
-      // Check for row swapping and vertical scrolling.
       if (y !== self.oldY) {
         self.rowObject.direction = y > self.oldY ? 'down' : 'up';
-        // Update the old value.
+
         self.oldY = y;
-        // Check if the window should be scrolled (and how fast).
+
         var scrollAmount = self.checkScroll(self.currentPointerCoords.y);
-        // Stop any current scrolling.
+
         clearInterval(self.scrollInterval);
-        // Continue scrolling if the mouse has moved in the scroll direction.
+
         if (scrollAmount > 0 && self.rowObject.direction === 'down' || scrollAmount < 0 && self.rowObject.direction === 'up') {
           self.setScroll(scrollAmount);
         }
 
-        // If we have a valid target, perform the swap and restripe the table.
         var currentRow = self.findDropTargetRow(x, y);
         if (currentRow) {
           if (self.rowObject.direction === 'down') {
             self.rowObject.swap('after', currentRow, self);
-          }
-          else {
+          } else {
             self.rowObject.swap('before', currentRow, self);
           }
           if (self.striping === true) {
@@ -736,16 +456,13 @@
         }
       }
 
-      // Similar to row swapping, handle indentations.
       if (self.indentEnabled) {
         var xDiff = self.currentPointerCoords.x - self.dragObject.indentPointerPos.x;
-        // Set the number of indentations the pointer has been moved left or
-        // right.
+
         var indentDiff = Math.round(xDiff / self.indentAmount);
-        // Indent the row with our estimated diff, which may be further
-        // restricted according to the rows around this row.
+
         var indentChange = self.rowObject.indent(indentDiff);
-        // Update table and pointer indentations.
+
         self.dragObject.indentPointerPos.x += self.indentAmount * indentChange * self.rtl;
         self.indentCount = Math.max(self.indentCount, self.rowObject.indents);
       }
@@ -754,29 +471,17 @@
     }
   };
 
-  /**
-   * Pointerup behavior.
-   *
-   * @param {jQuery.Event} event
-   *   The pointer event.
-   * @param {Drupal.tableDrag} self
-   *   The tableDrag instance.
-   */
   Drupal.tableDrag.prototype.dropRow = function (event, self) {
     var droppedRow;
     var $droppedRow;
 
-    // Drop row functionality.
     if (self.rowObject !== null) {
       droppedRow = self.rowObject.element;
       $droppedRow = $(droppedRow);
-      // The row is already in the right place so we just release it.
+
       if (self.rowObject.changed === true) {
-        // Update the fields in the dropped row.
         self.updateFields(droppedRow);
 
-        // If a setting exists for affecting the entire group, update all the
-        // fields in the entire dragged group.
         for (var group in self.tableSettings) {
           if (self.tableSettings.hasOwnProperty(group)) {
             var rowSettings = self.rowSettings(group, droppedRow);
@@ -809,7 +514,6 @@
       self.rowObject = null;
     }
 
-    // Functionality specific only to pointerup events.
     if (self.dragObject !== null) {
       self.dragObject = null;
       $('body').removeClass('drag');
@@ -817,18 +521,9 @@
     }
   };
 
-  /**
-   * Get the coordinates from the event (allowing for browser differences).
-   *
-   * @param {jQuery.Event} event
-   *   The pointer event.
-   *
-   * @return {object}
-   *   An object with `x` and `y` keys indicating the position.
-   */
   Drupal.tableDrag.prototype.pointerCoords = function (event) {
     if (event.pageX || event.pageY) {
-      return {x: event.pageX, y: event.pageY};
+      return { x: event.pageX, y: event.pageY };
     }
     return {
       x: event.clientX + document.body.scrollLeft - document.body.clientLeft,
@@ -836,39 +531,12 @@
     };
   };
 
-  /**
-   * Get the event offset from the target element.
-   *
-   * Given a target element and a pointer event, get the event offset from that
-   * element. To do this we need the element's position and the target position.
-   *
-   * @param {HTMLElement} target
-   *   The target HTML element.
-   * @param {jQuery.Event} event
-   *   The pointer event.
-   *
-   * @return {object}
-   *   An object with `x` and `y` keys indicating the position.
-   */
   Drupal.tableDrag.prototype.getPointerOffset = function (target, event) {
     var docPos = $(target).offset();
     var pointerPos = this.pointerCoords(event);
-    return {x: pointerPos.x - docPos.left, y: pointerPos.y - docPos.top};
+    return { x: pointerPos.x - docPos.left, y: pointerPos.y - docPos.top };
   };
 
-  /**
-   * Find the row the mouse is currently over.
-   *
-   * This row is then taken and swapped with the one being dragged.
-   *
-   * @param {number} x
-   *   The x coordinate of the mouse on the page (not the screen).
-   * @param {number} y
-   *   The y coordinate of the mouse on the page (not the screen).
-   *
-   * @return {*}
-   *   The drop target row, if found.
-   */
   Drupal.tableDrag.prototype.findDropTargetRow = function (x, y) {
     var rows = $(this.table.tBodies[0].rows).not(':hidden');
     for (var n = 0; n < rows.length; n++) {
@@ -876,42 +544,30 @@
       var $row = $(row);
       var rowY = $row.offset().top;
       var rowHeight;
-      // Because Safari does not report offsetHeight on table rows, but does on
-      // table cells, grab the firstChild of the row and use that instead.
-      // http://jacob.peargrove.com/blog/2006/technical/table-row-offsettop-bug-in-safari.
+
       if (row.offsetHeight === 0) {
         rowHeight = parseInt(row.firstChild.offsetHeight, 10) / 2;
-      }
-      // Other browsers.
-      else {
-        rowHeight = parseInt(row.offsetHeight, 10) / 2;
-      }
+      } else {
+          rowHeight = parseInt(row.offsetHeight, 10) / 2;
+        }
 
-      // Because we always insert before, we need to offset the height a bit.
-      if ((y > (rowY - rowHeight)) && (y < (rowY + rowHeight))) {
+      if (y > rowY - rowHeight && y < rowY + rowHeight) {
         if (this.indentEnabled) {
-          // Check that this row is not a child of the row being dragged.
           for (n in this.rowObject.group) {
             if (this.rowObject.group[n] === row) {
               return null;
             }
           }
-        }
-        else {
-          // Do not allow a row to be swapped with itself.
+        } else {
           if (row === this.rowObject.element) {
             return null;
           }
         }
 
-        // Check that swapping with this row is allowed.
         if (!this.rowObject.isValidSwap(row)) {
           return null;
         }
 
-        // We may have found the row the mouse just passed over, but it doesn't
-        // take into account hidden rows. Skip backwards until we find a
-        // draggable row.
         while ($row.is(':hidden') && $row.prev('tr').is(':hidden')) {
           $row = $row.prev('tr:first-of-type');
           row = $row.get(0);
@@ -922,30 +578,14 @@
     return null;
   };
 
-  /**
-   * After the row is dropped, update the table fields.
-   *
-   * @param {HTMLElement} changedRow
-   *   DOM object for the row that was just dropped.
-   */
   Drupal.tableDrag.prototype.updateFields = function (changedRow) {
     for (var group in this.tableSettings) {
       if (this.tableSettings.hasOwnProperty(group)) {
-        // Each group may have a different setting for relationship, so we find
-        // the source rows for each separately.
         this.updateField(changedRow, group);
       }
     }
   };
 
-  /**
-   * After the row is dropped, update a single table field.
-   *
-   * @param {HTMLElement} changedRow
-   *   DOM object for the row that was just dropped.
-   * @param {string} group
-   *   The settings group on which field updates will occur.
-   */
   Drupal.tableDrag.prototype.updateField = function (changedRow, group) {
     var rowSettings = this.rowSettings(group, changedRow);
     var $changedRow = $(changedRow);
@@ -953,72 +593,54 @@
     var $previousRow;
     var previousRow;
     var useSibling;
-    // Set the row as its own target.
+
     if (rowSettings.relationship === 'self' || rowSettings.relationship === 'group') {
       sourceRow = changedRow;
-    }
-    // Siblings are easy, check previous and next rows.
-    else if (rowSettings.relationship === 'sibling') {
-      $previousRow = $changedRow.prev('tr:first-of-type');
-      previousRow = $previousRow.get(0);
-      var $nextRow = $changedRow.next('tr:first-of-type');
-      var nextRow = $nextRow.get(0);
-      sourceRow = changedRow;
-      if ($previousRow.is('.draggable') && $previousRow.find('.' + group).length) {
-        if (this.indentEnabled) {
-          if ($previousRow.find('.js-indentations').length === $changedRow.find('.js-indentations').length) {
+    } else if (rowSettings.relationship === 'sibling') {
+        $previousRow = $changedRow.prev('tr:first-of-type');
+        previousRow = $previousRow.get(0);
+        var $nextRow = $changedRow.next('tr:first-of-type');
+        var nextRow = $nextRow.get(0);
+        sourceRow = changedRow;
+        if ($previousRow.is('.draggable') && $previousRow.find('.' + group).length) {
+          if (this.indentEnabled) {
+            if ($previousRow.find('.js-indentations').length === $changedRow.find('.js-indentations').length) {
+              sourceRow = previousRow;
+            }
+          } else {
             sourceRow = previousRow;
           }
-        }
-        else {
-          sourceRow = previousRow;
-        }
-      }
-      else if ($nextRow.is('.draggable') && $nextRow.find('.' + group).length) {
-        if (this.indentEnabled) {
-          if ($nextRow.find('.js-indentations').length === $changedRow.find('.js-indentations').length) {
+        } else if ($nextRow.is('.draggable') && $nextRow.find('.' + group).length) {
+          if (this.indentEnabled) {
+            if ($nextRow.find('.js-indentations').length === $changedRow.find('.js-indentations').length) {
+              sourceRow = nextRow;
+            }
+          } else {
             sourceRow = nextRow;
           }
         }
-        else {
-          sourceRow = nextRow;
-        }
-      }
-    }
-    // Parents, look up the tree until we find a field not in this group.
-    // Go up as many parents as indentations in the changed row.
-    else if (rowSettings.relationship === 'parent') {
-      $previousRow = $changedRow.prev('tr');
-      previousRow = $previousRow;
-      while ($previousRow.length && $previousRow.find('.js-indentation').length >= this.rowObject.indents) {
-        $previousRow = $previousRow.prev('tr');
-        previousRow = $previousRow;
-      }
-      // If we found a row.
-      if ($previousRow.length) {
-        sourceRow = $previousRow.get(0);
-      }
-      // Otherwise we went all the way to the left of the table without finding
-      // a parent, meaning this item has been placed at the root level.
-      else {
-        // Use the first row in the table as source, because it's guaranteed to
-        // be at the root level. Find the first item, then compare this row
-        // against it as a sibling.
-        sourceRow = $(this.table).find('tr.draggable:first-of-type').get(0);
-        if (sourceRow === this.rowObject.element) {
-          sourceRow = $(this.rowObject.group[this.rowObject.group.length - 1]).next('tr.draggable').get(0);
+      } else if (rowSettings.relationship === 'parent') {
+          $previousRow = $changedRow.prev('tr');
+          previousRow = $previousRow;
+          while ($previousRow.length && $previousRow.find('.js-indentation').length >= this.rowObject.indents) {
+            $previousRow = $previousRow.prev('tr');
+            previousRow = $previousRow;
+          }
+
+          if ($previousRow.length) {
+            sourceRow = $previousRow.get(0);
+          } else {
+              sourceRow = $(this.table).find('tr.draggable:first-of-type').get(0);
+              if (sourceRow === this.rowObject.element) {
+                sourceRow = $(this.rowObject.group[this.rowObject.group.length - 1]).next('tr.draggable').get(0);
+              }
+              useSibling = true;
+            }
         }
-        useSibling = true;
-      }
-    }
 
-    // Because we may have moved the row from one category to another,
-    // take a look at our sibling and borrow its sources and targets.
     this.copyDragClasses(sourceRow, changedRow, group);
     rowSettings = this.rowSettings(group, changedRow);
 
-    // In the case that we're looking for a parent, but the row is at the top
-    // of the tree, copy our sibling's values.
     if (useSibling) {
       rowSettings.relationship = 'sibling';
       rowSettings.source = rowSettings.target;
@@ -1027,44 +649,35 @@
     var targetClass = '.' + rowSettings.target;
     var targetElement = $changedRow.find(targetClass).get(0);
 
-    // Check if a target element exists in this row.
     if (targetElement) {
       var sourceClass = '.' + rowSettings.source;
       var sourceElement = $(sourceClass, sourceRow).get(0);
       switch (rowSettings.action) {
         case 'depth':
-          // Get the depth of the target row.
           targetElement.value = $(sourceElement).closest('tr').find('.js-indentation').length;
           break;
 
         case 'match':
-          // Update the value.
           targetElement.value = sourceElement.value;
           break;
 
         case 'order':
           var siblings = this.rowObject.findSiblings(rowSettings);
           if ($(targetElement).is('select')) {
-            // Get a list of acceptable values.
             var values = [];
             $(targetElement).find('option').each(function () {
               values.push(this.value);
             });
             var maxVal = values[values.length - 1];
-            // Populate the values in the siblings.
+
             $(siblings).find(targetClass).each(function () {
-              // If there are more items than possible values, assign the
-              // maximum value to the row.
               if (values.length > 0) {
                 this.value = values.shift();
-              }
-              else {
+              } else {
                 this.value = maxVal;
               }
             });
-          }
-          else {
-            // Assume a numeric input field.
+          } else {
             var weight = parseInt($(siblings[0]).find(targetClass).val(), 10) || 0;
             $(siblings).find(targetClass).each(function () {
               this.value = weight;
@@ -1076,20 +689,6 @@
     }
   };
 
-  /**
-   * Copy all tableDrag related classes from one row to another.
-   *
-   * Copy all special tableDrag classes from one row's form elements to a
-   * different one, removing any special classes that the destination row
-   * may have had.
-   *
-   * @param {HTMLElement} sourceRow
-   *   The element for the source row.
-   * @param {HTMLElement} targetRow
-   *   The element for the target row.
-   * @param {string} group
-   *   The group selector.
-   */
   Drupal.tableDrag.prototype.copyDragClasses = function (sourceRow, targetRow, group) {
     var sourceElement = $(sourceRow).find('.' + group);
     var targetElement = $(targetRow).find('.' + group);
@@ -1098,15 +697,6 @@
     }
   };
 
-  /**
-   * Check the suggested scroll of the table.
-   *
-   * @param {number} cursorY
-   *   The Y position of the cursor.
-   *
-   * @return {number}
-   *   The suggested scroll.
-   */
   Drupal.tableDrag.prototype.checkScroll = function (cursorY) {
     var de = document.documentElement;
     var b = document.body;
@@ -1115,37 +705,27 @@
     var scrollY;
     if (document.all) {
       scrollY = this.scrollY = !de.scrollTop ? b.scrollTop : de.scrollTop;
-    }
-    else {
+    } else {
       scrollY = this.scrollY = window.pageYOffset ? window.pageYOffset : window.scrollY;
     }
     var trigger = this.scrollSettings.trigger;
     var delta = 0;
 
-    // Return a scroll speed relative to the edge of the screen.
     if (cursorY - scrollY > windowHeight - trigger) {
       delta = trigger / (windowHeight + scrollY - cursorY);
-      delta = (delta > 0 && delta < trigger) ? delta : trigger;
+      delta = delta > 0 && delta < trigger ? delta : trigger;
       return delta * this.scrollSettings.amount;
-    }
-    else if (cursorY - scrollY < trigger) {
+    } else if (cursorY - scrollY < trigger) {
       delta = trigger / (cursorY - scrollY);
-      delta = (delta > 0 && delta < trigger) ? delta : trigger;
+      delta = delta > 0 && delta < trigger ? delta : trigger;
       return -delta * this.scrollSettings.amount;
     }
   };
 
-  /**
-   * Set the scroll for the table.
-   *
-   * @param {number} scrollAmount
-   *   The amount of scroll to apply to the window.
-   */
   Drupal.tableDrag.prototype.setScroll = function (scrollAmount) {
     var self = this;
 
     this.scrollInterval = setInterval(function () {
-      // Update the scroll values stored in the object.
       self.checkScroll(self.currentPointerCoords.y);
       var aboveTable = self.scrollY > self.table.topY;
       var belowTable = self.scrollY + self.windowHeight < self.table.bottomY;
@@ -1155,55 +735,18 @@
     }, this.scrollSettings.interval);
   };
 
-  /**
-   * Command to restripe table properly.
-   */
   Drupal.tableDrag.prototype.restripeTable = function () {
-    // :even and :odd are reversed because jQuery counts from 0 and
-    // we count from 1, so we're out of sync.
-    // Match immediate children of the parent element to allow nesting.
-    $(this.table).find('> tbody > tr.draggable, > tr.draggable')
-      .filter(':visible')
-      .filter(':odd').removeClass('odd').addClass('even').end()
-      .filter(':even').removeClass('even').addClass('odd');
+    $(this.table).find('> tbody > tr.draggable, > tr.draggable').filter(':visible').filter(':odd').removeClass('odd').addClass('even').end().filter(':even').removeClass('even').addClass('odd');
   };
 
-  /**
-   * Stub function. Allows a custom handler when a row begins dragging.
-   *
-   * @return {null}
-   *   Returns null when the stub function is used.
-   */
   Drupal.tableDrag.prototype.onDrag = function () {
     return null;
   };
 
-  /**
-   * Stub function. Allows a custom handler when a row is dropped.
-   *
-   * @return {null}
-   *   Returns null when the stub function is used.
-   */
   Drupal.tableDrag.prototype.onDrop = function () {
     return null;
   };
 
-  /**
-   * Constructor to make a new object to manipulate a table row.
-   *
-   * @param {HTMLElement} tableRow
-   *   The DOM element for the table row we will be manipulating.
-   * @param {string} method
-   *   The method in which this row is being moved. Either 'keyboard' or
-   *   'mouse'.
-   * @param {bool} indentEnabled
-   *   Whether the containing table uses indentations. Used for optimizations.
-   * @param {number} maxDepth
-   *   The maximum amount of indentations this row may contain.
-   * @param {bool} addClasses
-   *   Whether we want to add classes to this row to indicate child
-   *   relationships.
-   */
   Drupal.tableDrag.prototype.row = function (tableRow, method, indentEnabled, maxDepth, addClasses) {
     var $tableRow = $(tableRow);
 
@@ -1215,29 +758,19 @@
     this.table = $tableRow.closest('table')[0];
     this.indentEnabled = indentEnabled;
     this.maxDepth = maxDepth;
-    // Direction the row is being moved.
+
     this.direction = '';
     if (this.indentEnabled) {
       this.indents = $tableRow.find('.js-indentation').length;
       this.children = this.findChildren(addClasses);
       this.group = $.merge(this.group, this.children);
-      // Find the depth of this entire group.
+
       for (var n = 0; n < this.group.length; n++) {
         this.groupDepth = Math.max($(this.group[n]).find('.js-indentation').length, this.groupDepth);
       }
     }
   };
 
-  /**
-   * Find all children of rowObject by indentation.
-   *
-   * @param {bool} addClasses
-   *   Whether we want to add classes to this row to indicate child
-   *   relationships.
-   *
-   * @return {Array}
-   *   An array of children of the row.
-   */
   Drupal.tableDrag.prototype.row.prototype.findChildren = function (addClasses) {
     var parentIndentation = this.indents;
     var currentRow = $(this.element, this.table).next('tr.draggable');
@@ -1246,27 +779,24 @@
 
     function rowIndentation(indentNum, el) {
       var self = $(el);
-      if (child === 1 && (indentNum === parentIndentation)) {
+      if (child === 1 && indentNum === parentIndentation) {
         self.addClass('tree-child-first');
       }
       if (indentNum === parentIndentation) {
         self.addClass('tree-child');
-      }
-      else if (indentNum > parentIndentation) {
+      } else if (indentNum > parentIndentation) {
         self.addClass('tree-child-horizontal');
       }
     }
 
     while (currentRow.length) {
-      // A greater indentation indicates this is a child.
       if (currentRow.find('.js-indentation').length > parentIndentation) {
         child++;
         rows.push(currentRow[0]);
         if (addClasses) {
           currentRow.find('.js-indentation').each(rowIndentation);
         }
-      }
-      else {
+      } else {
         break;
       }
       currentRow = currentRow.next('tr.draggable');
@@ -1277,15 +807,6 @@
     return rows;
   };
 
-  /**
-   * Ensure that two rows are allowed to be swapped.
-   *
-   * @param {HTMLElement} row
-   *   DOM object for the row being considered for swapping.
-   *
-   * @return {bool}
-   *   Whether the swap is a valid swap or not.
-   */
   Drupal.tableDrag.prototype.row.prototype.isValidSwap = function (row) {
     var $row = $(row);
     if (this.indentEnabled) {
@@ -1294,20 +815,17 @@
       if (this.direction === 'down') {
         prevRow = row;
         nextRow = $row.next('tr').get(0);
-      }
-      else {
+      } else {
         prevRow = $row.prev('tr').get(0);
         nextRow = row;
       }
       this.interval = this.validIndentInterval(prevRow, nextRow);
 
-      // We have an invalid swap if the valid indentations interval is empty.
       if (this.interval.min > this.interval.max) {
         return false;
       }
     }
 
-    // Do not let an un-draggable first row have anything put before it.
     if (this.table.tBodies[0].rows[0] === row && $row.is(':not(.draggable)')) {
       return false;
     }
@@ -1315,21 +833,12 @@
     return true;
   };
 
-  /**
-   * Perform the swap between two rows.
-   *
-   * @param {string} position
-   *   Whether the swap will occur 'before' or 'after' the given row.
-   * @param {HTMLElement} row
-   *   DOM element what will be swapped with the row group.
-   */
   Drupal.tableDrag.prototype.row.prototype.swap = function (position, row) {
-    // Makes sure only DOM object are passed to Drupal.detachBehaviors().
     this.group.forEach(function (row) {
       Drupal.detachBehaviors(row, drupalSettings, 'move');
     });
     $(row)[position](this.group);
-    // Makes sure only DOM object are passed to Drupal.attachBehaviors()s.
+
     this.group.forEach(function (row) {
       Drupal.attachBehaviors(row, drupalSettings);
     });
@@ -1337,88 +846,50 @@
     this.onSwap(row);
   };
 
-  /**
-   * Determine the valid indentations interval for the row at a given position.
-   *
-   * @param {?HTMLElement} prevRow
-   *   DOM object for the row before the tested position
-   *   (or null for first position in the table).
-   * @param {?HTMLElement} nextRow
-   *   DOM object for the row after the tested position
-   *   (or null for last position in the table).
-   *
-   * @return {object}
-   *   An object with the keys `min` and `max` to indicate the valid indent
-   *   interval.
-   */
   Drupal.tableDrag.prototype.row.prototype.validIndentInterval = function (prevRow, nextRow) {
     var $prevRow = $(prevRow);
     var minIndent;
     var maxIndent;
 
-    // Minimum indentation:
-    // Do not orphan the next row.
     minIndent = nextRow ? $(nextRow).find('.js-indentation').length : 0;
 
-    // Maximum indentation:
     if (!prevRow || $prevRow.is(':not(.draggable)') || $(this.element).is('.tabledrag-root')) {
-      // Do not indent:
-      // - the first row in the table,
-      // - rows dragged below a non-draggable row,
-      // - 'root' rows.
       maxIndent = 0;
-    }
-    else {
-      // Do not go deeper than as a child of the previous row.
+    } else {
       maxIndent = $prevRow.find('.js-indentation').length + ($prevRow.is('.tabledrag-leaf') ? 0 : 1);
-      // Limit by the maximum allowed depth for the table.
+
       if (this.maxDepth) {
         maxIndent = Math.min(maxIndent, this.maxDepth - (this.groupDepth - this.indents));
       }
     }
 
-    return {min: minIndent, max: maxIndent};
+    return { min: minIndent, max: maxIndent };
   };
 
-  /**
-   * Indent a row within the legal bounds of the table.
-   *
-   * @param {number} indentDiff
-   *   The number of additional indentations proposed for the row (can be
-   *   positive or negative). This number will be adjusted to nearest valid
-   *   indentation level for the row.
-   *
-   * @return {number}
-   *   The number of indentations applied.
-   */
   Drupal.tableDrag.prototype.row.prototype.indent = function (indentDiff) {
     var $group = $(this.group);
-    // Determine the valid indentations interval if not available yet.
+
     if (!this.interval) {
       var prevRow = $(this.element).prev('tr').get(0);
       var nextRow = $group.eq(-1).next('tr').get(0);
       this.interval = this.validIndentInterval(prevRow, nextRow);
     }
 
-    // Adjust to the nearest valid indentation.
     var indent = this.indents + indentDiff;
     indent = Math.max(indent, this.interval.min);
     indent = Math.min(indent, this.interval.max);
     indentDiff = indent - this.indents;
 
     for (var n = 1; n <= Math.abs(indentDiff); n++) {
-      // Add or remove indentations.
       if (indentDiff < 0) {
         $group.find('.js-indentation:first-of-type').remove();
         this.indents--;
-      }
-      else {
+      } else {
         $group.find('td:first-of-type').prepend(Drupal.theme('tableDragIndentation'));
         this.indents++;
       }
     }
     if (indentDiff) {
-      // Update indentation for this row.
       this.changed = true;
       this.groupDepth += indentDiff;
       this.onIndent();
@@ -1427,18 +898,6 @@
     return indentDiff;
   };
 
-  /**
-   * Find all siblings for a row.
-   *
-   * According to its subgroup or indentation. Note that the passed-in row is
-   * included in the list of siblings.
-   *
-   * @param {object} rowSettings
-   *   The field settings we're using to identify what constitutes a sibling.
-   *
-   * @return {Array}
-   *   An array of siblings.
-   */
   Drupal.tableDrag.prototype.row.prototype.findSiblings = function (rowSettings) {
     var siblings = [];
     var directions = ['prev', 'next'];
@@ -1447,29 +906,22 @@
     for (var d = 0; d < directions.length; d++) {
       var checkRow = $(this.element)[directions[d]]();
       while (checkRow.length) {
-        // Check that the sibling contains a similar target field.
         if (checkRow.find('.' + rowSettings.target)) {
-          // Either add immediately if this is a flat table, or check to ensure
-          // that this row has the same level of indentation.
           if (this.indentEnabled) {
             checkRowIndentation = checkRow.find('.js-indentation').length;
           }
 
-          if (!(this.indentEnabled) || (checkRowIndentation === rowIndentation)) {
+          if (!this.indentEnabled || checkRowIndentation === rowIndentation) {
             siblings.push(checkRow[0]);
-          }
-          else if (checkRowIndentation < rowIndentation) {
-            // No need to keep looking for siblings when we get to a parent.
+          } else if (checkRowIndentation < rowIndentation) {
             break;
           }
-        }
-        else {
+        } else {
           break;
         }
         checkRow = checkRow[directions[d]]();
       }
-      // Since siblings are added in reverse order for previous, reverse the
-      // completed list of previous siblings. Add the current row and continue.
+
       if (directions[d] === 'prev') {
         siblings.reverse();
         siblings.push(this.element);
@@ -1478,24 +930,14 @@
     return siblings;
   };
 
-  /**
-   * Remove indentation helper classes from the current row group.
-   */
   Drupal.tableDrag.prototype.row.prototype.removeIndentClasses = function () {
     for (var n in this.children) {
       if (this.children.hasOwnProperty(n)) {
-        $(this.children[n]).find('.js-indentation')
-          .removeClass('tree-child')
-          .removeClass('tree-child-first')
-          .removeClass('tree-child-last')
-          .removeClass('tree-child-horizontal');
+        $(this.children[n]).find('.js-indentation').removeClass('tree-child').removeClass('tree-child-first').removeClass('tree-child-last').removeClass('tree-child-horizontal');
       }
     }
   };
 
-  /**
-   * Add an asterisk or other marker to the changed row.
-   */
   Drupal.tableDrag.prototype.row.prototype.markChanged = function () {
     var marker = Drupal.theme('tableDragChangedMarker');
     var cell = $(this.element).find('td:first-of-type');
@@ -1504,54 +946,25 @@
     }
   };
 
-  /**
-   * Stub function. Allows a custom handler when a row is indented.
-   *
-   * @return {null}
-   *   Returns null when the stub function is used.
-   */
   Drupal.tableDrag.prototype.row.prototype.onIndent = function () {
     return null;
   };
 
-  /**
-   * Stub function. Allows a custom handler when a row is swapped.
-   *
-   * @param {HTMLElement} swappedRow
-   *   The element for the swapped row.
-   *
-   * @return {null}
-   *   Returns null when the stub function is used.
-   */
   Drupal.tableDrag.prototype.row.prototype.onSwap = function (swappedRow) {
     return null;
   };
 
-  $.extend(Drupal.theme, /** @lends Drupal.theme */{
-
-    /**
-     * @return {string}
-     *  Markup for the marker.
-     */
-    tableDragChangedMarker: function () {
+  $.extend(Drupal.theme, {
+    tableDragChangedMarker: function tableDragChangedMarker() {
       return '<abbr class="warning tabledrag-changed" title="' + Drupal.t('Changed') + '">*</abbr>';
     },
 
-    /**
-     * @return {string}
-     *   Markup for the indentation.
-     */
-    tableDragIndentation: function () {
+    tableDragIndentation: function tableDragIndentation() {
       return '<div class="js-indentation indentation">&nbsp;</div>';
     },
 
-    /**
-     * @return {string}
-     *   Markup for the warning.
-     */
-    tableDragChangedWarning: function () {
+    tableDragChangedWarning: function tableDragChangedWarning() {
       return '<div class="tabledrag-changed-warning messages messages--warning" role="alert">' + Drupal.theme('tableDragChangedMarker') + ' ' + Drupal.t('You have unsaved changes.') + '</div>';
     }
   });
-
-})(jQuery, Drupal, drupalSettings);
+})(jQuery, Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/misc/tableheader.es6.js b/core/misc/tableheader.es6.js
new file mode 100644
index 000000000000..8fc1b048963c
--- /dev/null
+++ b/core/misc/tableheader.es6.js
@@ -0,0 +1,316 @@
+/**
+ * @file
+ * Sticky table headers.
+ */
+
+(function ($, Drupal, displace) {
+
+  'use strict';
+
+  /**
+   * Attaches sticky table headers.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches the sticky table header behavior.
+   */
+  Drupal.behaviors.tableHeader = {
+    attach: function (context) {
+      $(window).one('scroll.TableHeaderInit', {context: context}, tableHeaderInitHandler);
+    }
+  };
+
+  function scrollValue(position) {
+    return document.documentElement[position] || document.body[position];
+  }
+
+  // Select and initialize sticky table headers.
+  function tableHeaderInitHandler(e) {
+    var $tables = $(e.data.context).find('table.sticky-enabled').once('tableheader');
+    var il = $tables.length;
+    for (var i = 0; i < il; i++) {
+      TableHeader.tables.push(new TableHeader($tables[i]));
+    }
+    forTables('onScroll');
+  }
+
+  // Helper method to loop through tables and execute a method.
+  function forTables(method, arg) {
+    var tables = TableHeader.tables;
+    var il = tables.length;
+    for (var i = 0; i < il; i++) {
+      tables[i][method](arg);
+    }
+  }
+
+  function tableHeaderResizeHandler(e) {
+    forTables('recalculateSticky');
+  }
+
+  function tableHeaderOnScrollHandler(e) {
+    forTables('onScroll');
+  }
+
+  function tableHeaderOffsetChangeHandler(e, offsets) {
+    forTables('stickyPosition', offsets.top);
+  }
+
+  // Bind event that need to change all tables.
+  $(window).on({
+
+    /**
+     * When resizing table width can change, recalculate everything.
+     *
+     * @ignore
+     */
+    'resize.TableHeader': tableHeaderResizeHandler,
+
+    /**
+     * Bind only one event to take care of calling all scroll callbacks.
+     *
+     * @ignore
+     */
+    'scroll.TableHeader': tableHeaderOnScrollHandler
+  });
+  // Bind to custom Drupal events.
+  $(document).on({
+
+    /**
+     * Recalculate columns width when window is resized and when show/hide
+     * weight is triggered.
+     *
+     * @ignore
+     */
+    'columnschange.TableHeader': tableHeaderResizeHandler,
+
+    /**
+     * Recalculate TableHeader.topOffset when viewport is resized.
+     *
+     * @ignore
+     */
+    'drupalViewportOffsetChange.TableHeader': tableHeaderOffsetChangeHandler
+  });
+
+  /**
+   * Constructor for the tableHeader object. Provides sticky table headers.
+   *
+   * TableHeader will make the current table header stick to the top of the page
+   * if the table is very long.
+   *
+   * @constructor Drupal.TableHeader
+   *
+   * @param {HTMLElement} table
+   *   DOM object for the table to add a sticky header to.
+   *
+   * @listens event:columnschange
+   */
+  function TableHeader(table) {
+    var $table = $(table);
+
+    /**
+     * @name Drupal.TableHeader#$originalTable
+     *
+     * @type {HTMLElement}
+     */
+    this.$originalTable = $table;
+
+    /**
+     * @type {jQuery}
+     */
+    this.$originalHeader = $table.children('thead');
+
+    /**
+     * @type {jQuery}
+     */
+    this.$originalHeaderCells = this.$originalHeader.find('> tr > th');
+
+    /**
+     * @type {null|bool}
+     */
+    this.displayWeight = null;
+    this.$originalTable.addClass('sticky-table');
+    this.tableHeight = $table[0].clientHeight;
+    this.tableOffset = this.$originalTable.offset();
+
+    // React to columns change to avoid making checks in the scroll callback.
+    this.$originalTable.on('columnschange', {tableHeader: this}, function (e, display) {
+      var tableHeader = e.data.tableHeader;
+      if (tableHeader.displayWeight === null || tableHeader.displayWeight !== display) {
+        tableHeader.recalculateSticky();
+      }
+      tableHeader.displayWeight = display;
+    });
+
+    // Create and display sticky header.
+    this.createSticky();
+  }
+
+  /**
+   * Store the state of TableHeader.
+   */
+  $.extend(TableHeader, /** @lends Drupal.TableHeader */{
+
+    /**
+     * This will store the state of all processed tables.
+     *
+     * @type {Array.<Drupal.TableHeader>}
+     */
+    tables: []
+  });
+
+  /**
+   * Extend TableHeader prototype.
+   */
+  $.extend(TableHeader.prototype, /** @lends Drupal.TableHeader# */{
+
+    /**
+     * Minimum height in pixels for the table to have a sticky header.
+     *
+     * @type {number}
+     */
+    minHeight: 100,
+
+    /**
+     * Absolute position of the table on the page.
+     *
+     * @type {?Drupal~displaceOffset}
+     */
+    tableOffset: null,
+
+    /**
+     * Absolute position of the table on the page.
+     *
+     * @type {?number}
+     */
+    tableHeight: null,
+
+    /**
+     * Boolean storing the sticky header visibility state.
+     *
+     * @type {bool}
+     */
+    stickyVisible: false,
+
+    /**
+     * Create the duplicate header.
+     */
+    createSticky: function () {
+      // Clone the table header so it inherits original jQuery properties.
+      var $stickyHeader = this.$originalHeader.clone(true);
+      // Hide the table to avoid a flash of the header clone upon page load.
+      this.$stickyTable = $('<table class="sticky-header"/>')
+        .css({
+          visibility: 'hidden',
+          position: 'fixed',
+          top: '0px'
+        })
+        .append($stickyHeader)
+        .insertBefore(this.$originalTable);
+
+      this.$stickyHeaderCells = $stickyHeader.find('> tr > th');
+
+      // Initialize all computations.
+      this.recalculateSticky();
+    },
+
+    /**
+     * Set absolute position of sticky.
+     *
+     * @param {number} offsetTop
+     *   The top offset for the sticky header.
+     * @param {number} offsetLeft
+     *   The left offset for the sticky header.
+     *
+     * @return {jQuery}
+     *   The sticky table as a jQuery collection.
+     */
+    stickyPosition: function (offsetTop, offsetLeft) {
+      var css = {};
+      if (typeof offsetTop === 'number') {
+        css.top = offsetTop + 'px';
+      }
+      if (typeof offsetLeft === 'number') {
+        css.left = (this.tableOffset.left - offsetLeft) + 'px';
+      }
+      return this.$stickyTable.css(css);
+    },
+
+    /**
+     * Returns true if sticky is currently visible.
+     *
+     * @return {bool}
+     *   The visibility status.
+     */
+    checkStickyVisible: function () {
+      var scrollTop = scrollValue('scrollTop');
+      var tableTop = this.tableOffset.top - displace.offsets.top;
+      var tableBottom = tableTop + this.tableHeight;
+      var visible = false;
+
+      if (tableTop < scrollTop && scrollTop < (tableBottom - this.minHeight)) {
+        visible = true;
+      }
+
+      this.stickyVisible = visible;
+      return visible;
+    },
+
+    /**
+     * Check if sticky header should be displayed.
+     *
+     * This function is throttled to once every 250ms to avoid unnecessary
+     * calls.
+     *
+     * @param {jQuery.Event} e
+     *   The scroll event.
+     */
+    onScroll: function (e) {
+      this.checkStickyVisible();
+      // Track horizontal positioning relative to the viewport.
+      this.stickyPosition(null, scrollValue('scrollLeft'));
+      this.$stickyTable.css('visibility', this.stickyVisible ? 'visible' : 'hidden');
+    },
+
+    /**
+     * Event handler: recalculates position of the sticky table header.
+     *
+     * @param {jQuery.Event} event
+     *   Event being triggered.
+     */
+    recalculateSticky: function (event) {
+      // Update table size.
+      this.tableHeight = this.$originalTable[0].clientHeight;
+
+      // Update offset top.
+      displace.offsets.top = displace.calculateOffset('top');
+      this.tableOffset = this.$originalTable.offset();
+      this.stickyPosition(displace.offsets.top, scrollValue('scrollLeft'));
+
+      // Update columns width.
+      var $that = null;
+      var $stickyCell = null;
+      var display = null;
+      // Resize header and its cell widths.
+      // Only apply width to visible table cells. This prevents the header from
+      // displaying incorrectly when the sticky header is no longer visible.
+      var il = this.$originalHeaderCells.length;
+      for (var i = 0; i < il; i++) {
+        $that = $(this.$originalHeaderCells[i]);
+        $stickyCell = this.$stickyHeaderCells.eq($that.index());
+        display = $that.css('display');
+        if (display !== 'none') {
+          $stickyCell.css({width: $that.css('width'), display: display});
+        }
+        else {
+          $stickyCell.css('display', 'none');
+        }
+      }
+      this.$stickyTable.css('width', this.$originalTable.outerWidth());
+    }
+  });
+
+  // Expose constructor in the public space.
+  Drupal.TableHeader = TableHeader;
+
+}(jQuery, Drupal, window.parent.Drupal.displace));
diff --git a/core/misc/tableheader.js b/core/misc/tableheader.js
index 8fc1b048963c..ccbd89f833c2 100644
--- a/core/misc/tableheader.js
+++ b/core/misc/tableheader.js
@@ -1,23 +1,18 @@
 /**
- * @file
- * Sticky table headers.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./misc/tableheader.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, displace) {
 
   'use strict';
 
-  /**
-   * Attaches sticky table headers.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches the sticky table header behavior.
-   */
   Drupal.behaviors.tableHeader = {
-    attach: function (context) {
-      $(window).one('scroll.TableHeaderInit', {context: context}, tableHeaderInitHandler);
+    attach: function attach(context) {
+      $(window).one('scroll.TableHeaderInit', { context: context }, tableHeaderInitHandler);
     }
   };
 
@@ -25,7 +20,6 @@
     return document.documentElement[position] || document.body[position];
   }
 
-  // Select and initialize sticky table headers.
   function tableHeaderInitHandler(e) {
     var $tables = $(e.data.context).find('table.sticky-enabled').once('tableheader');
     var il = $tables.length;
@@ -35,7 +29,6 @@
     forTables('onScroll');
   }
 
-  // Helper method to loop through tables and execute a method.
   function forTables(method, arg) {
     var tables = TableHeader.tables;
     var il = tables.length;
@@ -56,85 +49,33 @@
     forTables('stickyPosition', offsets.top);
   }
 
-  // Bind event that need to change all tables.
   $(window).on({
-
-    /**
-     * When resizing table width can change, recalculate everything.
-     *
-     * @ignore
-     */
     'resize.TableHeader': tableHeaderResizeHandler,
 
-    /**
-     * Bind only one event to take care of calling all scroll callbacks.
-     *
-     * @ignore
-     */
     'scroll.TableHeader': tableHeaderOnScrollHandler
   });
-  // Bind to custom Drupal events.
-  $(document).on({
 
-    /**
-     * Recalculate columns width when window is resized and when show/hide
-     * weight is triggered.
-     *
-     * @ignore
-     */
+  $(document).on({
     'columnschange.TableHeader': tableHeaderResizeHandler,
 
-    /**
-     * Recalculate TableHeader.topOffset when viewport is resized.
-     *
-     * @ignore
-     */
     'drupalViewportOffsetChange.TableHeader': tableHeaderOffsetChangeHandler
   });
 
-  /**
-   * Constructor for the tableHeader object. Provides sticky table headers.
-   *
-   * TableHeader will make the current table header stick to the top of the page
-   * if the table is very long.
-   *
-   * @constructor Drupal.TableHeader
-   *
-   * @param {HTMLElement} table
-   *   DOM object for the table to add a sticky header to.
-   *
-   * @listens event:columnschange
-   */
   function TableHeader(table) {
     var $table = $(table);
 
-    /**
-     * @name Drupal.TableHeader#$originalTable
-     *
-     * @type {HTMLElement}
-     */
     this.$originalTable = $table;
 
-    /**
-     * @type {jQuery}
-     */
     this.$originalHeader = $table.children('thead');
 
-    /**
-     * @type {jQuery}
-     */
     this.$originalHeaderCells = this.$originalHeader.find('> tr > th');
 
-    /**
-     * @type {null|bool}
-     */
     this.displayWeight = null;
     this.$originalTable.addClass('sticky-table');
     this.tableHeight = $table[0].clientHeight;
     this.tableOffset = this.$originalTable.offset();
 
-    // React to columns change to avoid making checks in the scroll callback.
-    this.$originalTable.on('columnschange', {tableHeader: this}, function (e, display) {
+    this.$originalTable.on('columnschange', { tableHeader: this }, function (e, display) {
       var tableHeader = e.data.tableHeader;
       if (tableHeader.displayWeight === null || tableHeader.displayWeight !== display) {
         tableHeader.recalculateSticky();
@@ -142,113 +83,54 @@
       tableHeader.displayWeight = display;
     });
 
-    // Create and display sticky header.
     this.createSticky();
   }
 
-  /**
-   * Store the state of TableHeader.
-   */
-  $.extend(TableHeader, /** @lends Drupal.TableHeader */{
-
-    /**
-     * This will store the state of all processed tables.
-     *
-     * @type {Array.<Drupal.TableHeader>}
-     */
+  $.extend(TableHeader, {
     tables: []
   });
 
-  /**
-   * Extend TableHeader prototype.
-   */
-  $.extend(TableHeader.prototype, /** @lends Drupal.TableHeader# */{
-
-    /**
-     * Minimum height in pixels for the table to have a sticky header.
-     *
-     * @type {number}
-     */
+  $.extend(TableHeader.prototype, {
     minHeight: 100,
 
-    /**
-     * Absolute position of the table on the page.
-     *
-     * @type {?Drupal~displaceOffset}
-     */
     tableOffset: null,
 
-    /**
-     * Absolute position of the table on the page.
-     *
-     * @type {?number}
-     */
     tableHeight: null,
 
-    /**
-     * Boolean storing the sticky header visibility state.
-     *
-     * @type {bool}
-     */
     stickyVisible: false,
 
-    /**
-     * Create the duplicate header.
-     */
-    createSticky: function () {
-      // Clone the table header so it inherits original jQuery properties.
+    createSticky: function createSticky() {
       var $stickyHeader = this.$originalHeader.clone(true);
-      // Hide the table to avoid a flash of the header clone upon page load.
-      this.$stickyTable = $('<table class="sticky-header"/>')
-        .css({
-          visibility: 'hidden',
-          position: 'fixed',
-          top: '0px'
-        })
-        .append($stickyHeader)
-        .insertBefore(this.$originalTable);
+
+      this.$stickyTable = $('<table class="sticky-header"/>').css({
+        visibility: 'hidden',
+        position: 'fixed',
+        top: '0px'
+      }).append($stickyHeader).insertBefore(this.$originalTable);
 
       this.$stickyHeaderCells = $stickyHeader.find('> tr > th');
 
-      // Initialize all computations.
       this.recalculateSticky();
     },
 
-    /**
-     * Set absolute position of sticky.
-     *
-     * @param {number} offsetTop
-     *   The top offset for the sticky header.
-     * @param {number} offsetLeft
-     *   The left offset for the sticky header.
-     *
-     * @return {jQuery}
-     *   The sticky table as a jQuery collection.
-     */
-    stickyPosition: function (offsetTop, offsetLeft) {
+    stickyPosition: function stickyPosition(offsetTop, offsetLeft) {
       var css = {};
       if (typeof offsetTop === 'number') {
         css.top = offsetTop + 'px';
       }
       if (typeof offsetLeft === 'number') {
-        css.left = (this.tableOffset.left - offsetLeft) + 'px';
+        css.left = this.tableOffset.left - offsetLeft + 'px';
       }
       return this.$stickyTable.css(css);
     },
 
-    /**
-     * Returns true if sticky is currently visible.
-     *
-     * @return {bool}
-     *   The visibility status.
-     */
-    checkStickyVisible: function () {
+    checkStickyVisible: function checkStickyVisible() {
       var scrollTop = scrollValue('scrollTop');
       var tableTop = this.tableOffset.top - displace.offsets.top;
       var tableBottom = tableTop + this.tableHeight;
       var visible = false;
 
-      if (tableTop < scrollTop && scrollTop < (tableBottom - this.minHeight)) {
+      if (tableTop < scrollTop && scrollTop < tableBottom - this.minHeight) {
         visible = true;
       }
 
@@ -256,53 +138,32 @@
       return visible;
     },
 
-    /**
-     * Check if sticky header should be displayed.
-     *
-     * This function is throttled to once every 250ms to avoid unnecessary
-     * calls.
-     *
-     * @param {jQuery.Event} e
-     *   The scroll event.
-     */
-    onScroll: function (e) {
+    onScroll: function onScroll(e) {
       this.checkStickyVisible();
-      // Track horizontal positioning relative to the viewport.
+
       this.stickyPosition(null, scrollValue('scrollLeft'));
       this.$stickyTable.css('visibility', this.stickyVisible ? 'visible' : 'hidden');
     },
 
-    /**
-     * Event handler: recalculates position of the sticky table header.
-     *
-     * @param {jQuery.Event} event
-     *   Event being triggered.
-     */
-    recalculateSticky: function (event) {
-      // Update table size.
+    recalculateSticky: function recalculateSticky(event) {
       this.tableHeight = this.$originalTable[0].clientHeight;
 
-      // Update offset top.
       displace.offsets.top = displace.calculateOffset('top');
       this.tableOffset = this.$originalTable.offset();
       this.stickyPosition(displace.offsets.top, scrollValue('scrollLeft'));
 
-      // Update columns width.
       var $that = null;
       var $stickyCell = null;
       var display = null;
-      // Resize header and its cell widths.
-      // Only apply width to visible table cells. This prevents the header from
-      // displaying incorrectly when the sticky header is no longer visible.
+
       var il = this.$originalHeaderCells.length;
       for (var i = 0; i < il; i++) {
         $that = $(this.$originalHeaderCells[i]);
         $stickyCell = this.$stickyHeaderCells.eq($that.index());
         display = $that.css('display');
         if (display !== 'none') {
-          $stickyCell.css({width: $that.css('width'), display: display});
-        }
-        else {
+          $stickyCell.css({ width: $that.css('width'), display: display });
+        } else {
           $stickyCell.css('display', 'none');
         }
       }
@@ -310,7 +171,5 @@
     }
   });
 
-  // Expose constructor in the public space.
   Drupal.TableHeader = TableHeader;
-
-}(jQuery, Drupal, window.parent.Drupal.displace));
+})(jQuery, Drupal, window.parent.Drupal.displace);
\ No newline at end of file
diff --git a/core/misc/tableresponsive.es6.js b/core/misc/tableresponsive.es6.js
new file mode 100644
index 000000000000..0127ec88735b
--- /dev/null
+++ b/core/misc/tableresponsive.es6.js
@@ -0,0 +1,174 @@
+/**
+ * @file
+ * Responsive table functionality.
+ */
+
+(function ($, Drupal, window) {
+
+  'use strict';
+
+  /**
+   * Attach the tableResponsive function to {@link Drupal.behaviors}.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches tableResponsive functionality.
+   */
+  Drupal.behaviors.tableResponsive = {
+    attach: function (context, settings) {
+      var $tables = $(context).find('table.responsive-enabled').once('tableresponsive');
+      if ($tables.length) {
+        var il = $tables.length;
+        for (var i = 0; i < il; i++) {
+          TableResponsive.tables.push(new TableResponsive($tables[i]));
+        }
+      }
+    }
+  };
+
+  /**
+   * The TableResponsive object optimizes table presentation for screen size.
+   *
+   * A responsive table hides columns at small screen sizes, leaving the most
+   * important columns visible to the end user. Users should not be prevented
+   * from accessing all columns, however. This class adds a toggle to a table
+   * with hidden columns that exposes the columns. Exposing the columns will
+   * likely break layouts, but it provides the user with a means to access
+   * data, which is a guiding principle of responsive design.
+   *
+   * @constructor Drupal.TableResponsive
+   *
+   * @param {HTMLElement} table
+   *   The table element to initialize the responsive table on.
+   */
+  function TableResponsive(table) {
+    this.table = table;
+    this.$table = $(table);
+    this.showText = Drupal.t('Show all columns');
+    this.hideText = Drupal.t('Hide lower priority columns');
+    // Store a reference to the header elements of the table so that the DOM is
+    // traversed only once to find them.
+    this.$headers = this.$table.find('th');
+    // Add a link before the table for users to show or hide weight columns.
+    this.$link = $('<button type="button" class="link tableresponsive-toggle"></button>')
+      .attr('title', Drupal.t('Show table cells that were hidden to make the table fit within a small screen.'))
+      .on('click', $.proxy(this, 'eventhandlerToggleColumns'));
+
+    this.$table.before($('<div class="tableresponsive-toggle-columns"></div>').append(this.$link));
+
+    // Attach a resize handler to the window.
+    $(window)
+      .on('resize.tableresponsive', $.proxy(this, 'eventhandlerEvaluateColumnVisibility'))
+      .trigger('resize.tableresponsive');
+  }
+
+  /**
+   * Extend the TableResponsive function with a list of managed tables.
+   */
+  $.extend(TableResponsive, /** @lends Drupal.TableResponsive */{
+
+    /**
+     * Store all created instances.
+     *
+     * @type {Array.<Drupal.TableResponsive>}
+     */
+    tables: []
+  });
+
+  /**
+   * Associates an action link with the table that will show hidden columns.
+   *
+   * Columns are assumed to be hidden if their header has the class priority-low
+   * or priority-medium.
+   */
+  $.extend(TableResponsive.prototype, /** @lends Drupal.TableResponsive# */{
+
+    /**
+     * @param {jQuery.Event} e
+     *   The event triggered.
+     */
+    eventhandlerEvaluateColumnVisibility: function (e) {
+      var pegged = parseInt(this.$link.data('pegged'), 10);
+      var hiddenLength = this.$headers.filter('.priority-medium:hidden, .priority-low:hidden').length;
+      // If the table has hidden columns, associate an action link with the
+      // table to show the columns.
+      if (hiddenLength > 0) {
+        this.$link.show().text(this.showText);
+      }
+      // When the toggle is pegged, its presence is maintained because the user
+      // has interacted with it. This is necessary to keep the link visible if
+      // the user adjusts screen size and changes the visibility of columns.
+      if (!pegged && hiddenLength === 0) {
+        this.$link.hide().text(this.hideText);
+      }
+    },
+
+    /**
+     * Toggle the visibility of columns based on their priority.
+     *
+     * Columns are classed with either 'priority-low' or 'priority-medium'.
+     *
+     * @param {jQuery.Event} e
+     *   The event triggered.
+     */
+    eventhandlerToggleColumns: function (e) {
+      e.preventDefault();
+      var self = this;
+      var $hiddenHeaders = this.$headers.filter('.priority-medium:hidden, .priority-low:hidden');
+      this.$revealedCells = this.$revealedCells || $();
+      // Reveal hidden columns.
+      if ($hiddenHeaders.length > 0) {
+        $hiddenHeaders.each(function (index, element) {
+          var $header = $(this);
+          var position = $header.prevAll('th').length;
+          self.$table.find('tbody tr').each(function () {
+            var $cells = $(this).find('td').eq(position);
+            $cells.show();
+            // Keep track of the revealed cells, so they can be hidden later.
+            self.$revealedCells = $().add(self.$revealedCells).add($cells);
+          });
+          $header.show();
+          // Keep track of the revealed headers, so they can be hidden later.
+          self.$revealedCells = $().add(self.$revealedCells).add($header);
+        });
+        this.$link.text(this.hideText).data('pegged', 1);
+      }
+      // Hide revealed columns.
+      else {
+        this.$revealedCells.hide();
+        // Strip the 'display:none' declaration from the style attributes of
+        // the table cells that .hide() added.
+        this.$revealedCells.each(function (index, element) {
+          var $cell = $(this);
+          var properties = $cell.attr('style').split(';');
+          var newProps = [];
+          // The hide method adds display none to the element. The element
+          // should be returned to the same state it was in before the columns
+          // were revealed, so it is necessary to remove the display none value
+          // from the style attribute.
+          var match = /^display\s*\:\s*none$/;
+          for (var i = 0; i < properties.length; i++) {
+            var prop = properties[i];
+            prop.trim();
+            // Find the display:none property and remove it.
+            var isDisplayNone = match.exec(prop);
+            if (isDisplayNone) {
+              continue;
+            }
+            newProps.push(prop);
+          }
+          // Return the rest of the style attribute values to the element.
+          $cell.attr('style', newProps.join(';'));
+        });
+        this.$link.text(this.showText).data('pegged', 0);
+        // Refresh the toggle link.
+        $(window).trigger('resize.tableresponsive');
+      }
+    }
+  });
+
+  // Make the TableResponsive object available in the Drupal namespace.
+  Drupal.TableResponsive = TableResponsive;
+
+})(jQuery, Drupal, window);
diff --git a/core/misc/tableresponsive.js b/core/misc/tableresponsive.js
index 0127ec88735b..c42530d8c97c 100644
--- a/core/misc/tableresponsive.js
+++ b/core/misc/tableresponsive.js
@@ -1,22 +1,17 @@
 /**
- * @file
- * Responsive table functionality.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./misc/tableresponsive.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, window) {
 
   'use strict';
 
-  /**
-   * Attach the tableResponsive function to {@link Drupal.behaviors}.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches tableResponsive functionality.
-   */
   Drupal.behaviors.tableResponsive = {
-    attach: function (context, settings) {
+    attach: function attach(context, settings) {
       var $tables = $(context).find('table.responsive-enabled').once('tableresponsive');
       if ($tables.length) {
         var il = $tables.length;
@@ -27,97 +22,45 @@
     }
   };
 
-  /**
-   * The TableResponsive object optimizes table presentation for screen size.
-   *
-   * A responsive table hides columns at small screen sizes, leaving the most
-   * important columns visible to the end user. Users should not be prevented
-   * from accessing all columns, however. This class adds a toggle to a table
-   * with hidden columns that exposes the columns. Exposing the columns will
-   * likely break layouts, but it provides the user with a means to access
-   * data, which is a guiding principle of responsive design.
-   *
-   * @constructor Drupal.TableResponsive
-   *
-   * @param {HTMLElement} table
-   *   The table element to initialize the responsive table on.
-   */
   function TableResponsive(table) {
     this.table = table;
     this.$table = $(table);
     this.showText = Drupal.t('Show all columns');
     this.hideText = Drupal.t('Hide lower priority columns');
-    // Store a reference to the header elements of the table so that the DOM is
-    // traversed only once to find them.
+
     this.$headers = this.$table.find('th');
-    // Add a link before the table for users to show or hide weight columns.
-    this.$link = $('<button type="button" class="link tableresponsive-toggle"></button>')
-      .attr('title', Drupal.t('Show table cells that were hidden to make the table fit within a small screen.'))
-      .on('click', $.proxy(this, 'eventhandlerToggleColumns'));
+
+    this.$link = $('<button type="button" class="link tableresponsive-toggle"></button>').attr('title', Drupal.t('Show table cells that were hidden to make the table fit within a small screen.')).on('click', $.proxy(this, 'eventhandlerToggleColumns'));
 
     this.$table.before($('<div class="tableresponsive-toggle-columns"></div>').append(this.$link));
 
-    // Attach a resize handler to the window.
-    $(window)
-      .on('resize.tableresponsive', $.proxy(this, 'eventhandlerEvaluateColumnVisibility'))
-      .trigger('resize.tableresponsive');
+    $(window).on('resize.tableresponsive', $.proxy(this, 'eventhandlerEvaluateColumnVisibility')).trigger('resize.tableresponsive');
   }
 
-  /**
-   * Extend the TableResponsive function with a list of managed tables.
-   */
-  $.extend(TableResponsive, /** @lends Drupal.TableResponsive */{
-
-    /**
-     * Store all created instances.
-     *
-     * @type {Array.<Drupal.TableResponsive>}
-     */
+  $.extend(TableResponsive, {
     tables: []
   });
 
-  /**
-   * Associates an action link with the table that will show hidden columns.
-   *
-   * Columns are assumed to be hidden if their header has the class priority-low
-   * or priority-medium.
-   */
-  $.extend(TableResponsive.prototype, /** @lends Drupal.TableResponsive# */{
-
-    /**
-     * @param {jQuery.Event} e
-     *   The event triggered.
-     */
-    eventhandlerEvaluateColumnVisibility: function (e) {
+  $.extend(TableResponsive.prototype, {
+    eventhandlerEvaluateColumnVisibility: function eventhandlerEvaluateColumnVisibility(e) {
       var pegged = parseInt(this.$link.data('pegged'), 10);
       var hiddenLength = this.$headers.filter('.priority-medium:hidden, .priority-low:hidden').length;
-      // If the table has hidden columns, associate an action link with the
-      // table to show the columns.
+
       if (hiddenLength > 0) {
         this.$link.show().text(this.showText);
       }
-      // When the toggle is pegged, its presence is maintained because the user
-      // has interacted with it. This is necessary to keep the link visible if
-      // the user adjusts screen size and changes the visibility of columns.
+
       if (!pegged && hiddenLength === 0) {
         this.$link.hide().text(this.hideText);
       }
     },
 
-    /**
-     * Toggle the visibility of columns based on their priority.
-     *
-     * Columns are classed with either 'priority-low' or 'priority-medium'.
-     *
-     * @param {jQuery.Event} e
-     *   The event triggered.
-     */
-    eventhandlerToggleColumns: function (e) {
+    eventhandlerToggleColumns: function eventhandlerToggleColumns(e) {
       e.preventDefault();
       var self = this;
       var $hiddenHeaders = this.$headers.filter('.priority-medium:hidden, .priority-low:hidden');
       this.$revealedCells = this.$revealedCells || $();
-      // Reveal hidden columns.
+
       if ($hiddenHeaders.length > 0) {
         $hiddenHeaders.each(function (index, element) {
           var $header = $(this);
@@ -125,50 +68,42 @@
           self.$table.find('tbody tr').each(function () {
             var $cells = $(this).find('td').eq(position);
             $cells.show();
-            // Keep track of the revealed cells, so they can be hidden later.
+
             self.$revealedCells = $().add(self.$revealedCells).add($cells);
           });
           $header.show();
-          // Keep track of the revealed headers, so they can be hidden later.
+
           self.$revealedCells = $().add(self.$revealedCells).add($header);
         });
         this.$link.text(this.hideText).data('pegged', 1);
-      }
-      // Hide revealed columns.
-      else {
-        this.$revealedCells.hide();
-        // Strip the 'display:none' declaration from the style attributes of
-        // the table cells that .hide() added.
-        this.$revealedCells.each(function (index, element) {
-          var $cell = $(this);
-          var properties = $cell.attr('style').split(';');
-          var newProps = [];
-          // The hide method adds display none to the element. The element
-          // should be returned to the same state it was in before the columns
-          // were revealed, so it is necessary to remove the display none value
-          // from the style attribute.
-          var match = /^display\s*\:\s*none$/;
-          for (var i = 0; i < properties.length; i++) {
-            var prop = properties[i];
-            prop.trim();
-            // Find the display:none property and remove it.
-            var isDisplayNone = match.exec(prop);
-            if (isDisplayNone) {
-              continue;
+      } else {
+          this.$revealedCells.hide();
+
+          this.$revealedCells.each(function (index, element) {
+            var $cell = $(this);
+            var properties = $cell.attr('style').split(';');
+            var newProps = [];
+
+            var match = /^display\s*\:\s*none$/;
+            for (var i = 0; i < properties.length; i++) {
+              var prop = properties[i];
+              prop.trim();
+
+              var isDisplayNone = match.exec(prop);
+              if (isDisplayNone) {
+                continue;
+              }
+              newProps.push(prop);
             }
-            newProps.push(prop);
-          }
-          // Return the rest of the style attribute values to the element.
-          $cell.attr('style', newProps.join(';'));
-        });
-        this.$link.text(this.showText).data('pegged', 0);
-        // Refresh the toggle link.
-        $(window).trigger('resize.tableresponsive');
-      }
+
+            $cell.attr('style', newProps.join(';'));
+          });
+          this.$link.text(this.showText).data('pegged', 0);
+
+          $(window).trigger('resize.tableresponsive');
+        }
     }
   });
 
-  // Make the TableResponsive object available in the Drupal namespace.
   Drupal.TableResponsive = TableResponsive;
-
-})(jQuery, Drupal, window);
+})(jQuery, Drupal, window);
\ No newline at end of file
diff --git a/core/misc/tableselect.es6.js b/core/misc/tableselect.es6.js
new file mode 100644
index 000000000000..243e0001107d
--- /dev/null
+++ b/core/misc/tableselect.es6.js
@@ -0,0 +1,159 @@
+/**
+ * @file
+ * Table select functionality.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * Initialize tableSelects.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches tableSelect functionality.
+   */
+  Drupal.behaviors.tableSelect = {
+    attach: function (context, settings) {
+      // Select the inner-most table in case of nested tables.
+      $(context).find('th.select-all').closest('table').once('table-select').each(Drupal.tableSelect);
+    }
+  };
+
+  /**
+   * Callback used in {@link Drupal.behaviors.tableSelect}.
+   */
+  Drupal.tableSelect = function () {
+    // Do not add a "Select all" checkbox if there are no rows with checkboxes
+    // in the table.
+    if ($(this).find('td input[type="checkbox"]').length === 0) {
+      return;
+    }
+
+    // Keep track of the table, which checkbox is checked and alias the
+    // settings.
+    var table = this;
+    var checkboxes;
+    var lastChecked;
+    var $table = $(table);
+    var strings = {
+      selectAll: Drupal.t('Select all rows in this table'),
+      selectNone: Drupal.t('Deselect all rows in this table')
+    };
+    var updateSelectAll = function (state) {
+      // Update table's select-all checkbox (and sticky header's if available).
+      $table.prev('table.sticky-header').addBack().find('th.select-all input[type="checkbox"]').each(function () {
+        var $checkbox = $(this);
+        var stateChanged = $checkbox.prop('checked') !== state;
+
+        $checkbox.attr('title', state ? strings.selectNone : strings.selectAll);
+
+        /**
+         * @checkbox {HTMLElement}
+         */
+        if (stateChanged) {
+          $checkbox.prop('checked', state).trigger('change');
+        }
+      });
+    };
+
+    // Find all <th> with class select-all, and insert the check all checkbox.
+    $table.find('th.select-all').prepend($('<input type="checkbox" class="form-checkbox" />').attr('title', strings.selectAll)).on('click', function (event) {
+      if ($(event.target).is('input[type="checkbox"]')) {
+        // Loop through all checkboxes and set their state to the select all
+        // checkbox' state.
+        checkboxes.each(function () {
+          var $checkbox = $(this);
+          var stateChanged = $checkbox.prop('checked') !== event.target.checked;
+
+          /**
+           * @checkbox {HTMLElement}
+           */
+          if (stateChanged) {
+            $checkbox.prop('checked', event.target.checked).trigger('change');
+          }
+          // Either add or remove the selected class based on the state of the
+          // check all checkbox.
+
+          /**
+           * @checkbox {HTMLElement}
+           */
+          $checkbox.closest('tr').toggleClass('selected', this.checked);
+        });
+        // Update the title and the state of the check all box.
+        updateSelectAll(event.target.checked);
+      }
+    });
+
+    // For each of the checkboxes within the table that are not disabled.
+    checkboxes = $table.find('td input[type="checkbox"]:enabled').on('click', function (e) {
+      // Either add or remove the selected class based on the state of the
+      // check all checkbox.
+
+      /**
+       * @this {HTMLElement}
+       */
+      $(this).closest('tr').toggleClass('selected', this.checked);
+
+      // If this is a shift click, we need to highlight everything in the
+      // range. Also make sure that we are actually checking checkboxes
+      // over a range and that a checkbox has been checked or unchecked before.
+      if (e.shiftKey && lastChecked && lastChecked !== e.target) {
+        // We use the checkbox's parent <tr> to do our range searching.
+        Drupal.tableSelectRange($(e.target).closest('tr')[0], $(lastChecked).closest('tr')[0], e.target.checked);
+      }
+
+      // If all checkboxes are checked, make sure the select-all one is checked
+      // too, otherwise keep unchecked.
+      updateSelectAll((checkboxes.length === checkboxes.filter(':checked').length));
+
+      // Keep track of the last checked checkbox.
+      lastChecked = e.target;
+    });
+
+    // If all checkboxes are checked on page load, make sure the select-all one
+    // is checked too, otherwise keep unchecked.
+    updateSelectAll((checkboxes.length === checkboxes.filter(':checked').length));
+  };
+
+  /**
+   * @param {HTMLElement} from
+   *   The HTML element representing the "from" part of the range.
+   * @param {HTMLElement} to
+   *   The HTML element representing the "to" part of the range.
+   * @param {bool} state
+   *   The state to set on the range.
+   */
+  Drupal.tableSelectRange = function (from, to, state) {
+    // We determine the looping mode based on the order of from and to.
+    var mode = from.rowIndex > to.rowIndex ? 'previousSibling' : 'nextSibling';
+
+    // Traverse through the sibling nodes.
+    for (var i = from[mode]; i; i = i[mode]) {
+      var $i;
+      // Make sure that we're only dealing with elements.
+      if (i.nodeType !== 1) {
+        continue;
+      }
+      $i = $(i);
+      // Either add or remove the selected class based on the state of the
+      // target checkbox.
+      $i.toggleClass('selected', state);
+      $i.find('input[type="checkbox"]').prop('checked', state);
+
+      if (to.nodeType) {
+        // If we are at the end of the range, stop.
+        if (i === to) {
+          break;
+        }
+      }
+      // A faster alternative to doing $(i).filter(to).length.
+      else if ($.filter(to, [i]).r.length) {
+        break;
+      }
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/core/misc/tableselect.js b/core/misc/tableselect.js
index 243e0001107d..a111fbeacebb 100644
--- a/core/misc/tableselect.js
+++ b/core/misc/tableselect.js
@@ -1,39 +1,26 @@
 /**
- * @file
- * Table select functionality.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./misc/tableselect.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   * Initialize tableSelects.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches tableSelect functionality.
-   */
   Drupal.behaviors.tableSelect = {
-    attach: function (context, settings) {
-      // Select the inner-most table in case of nested tables.
+    attach: function attach(context, settings) {
       $(context).find('th.select-all').closest('table').once('table-select').each(Drupal.tableSelect);
     }
   };
 
-  /**
-   * Callback used in {@link Drupal.behaviors.tableSelect}.
-   */
   Drupal.tableSelect = function () {
-    // Do not add a "Select all" checkbox if there are no rows with checkboxes
-    // in the table.
     if ($(this).find('td input[type="checkbox"]').length === 0) {
       return;
     }
 
-    // Keep track of the table, which checkbox is checked and alias the
-    // settings.
     var table = this;
     var checkboxes;
     var lastChecked;
@@ -42,118 +29,72 @@
       selectAll: Drupal.t('Select all rows in this table'),
       selectNone: Drupal.t('Deselect all rows in this table')
     };
-    var updateSelectAll = function (state) {
-      // Update table's select-all checkbox (and sticky header's if available).
+    var updateSelectAll = function updateSelectAll(state) {
       $table.prev('table.sticky-header').addBack().find('th.select-all input[type="checkbox"]').each(function () {
         var $checkbox = $(this);
         var stateChanged = $checkbox.prop('checked') !== state;
 
         $checkbox.attr('title', state ? strings.selectNone : strings.selectAll);
 
-        /**
-         * @checkbox {HTMLElement}
-         */
         if (stateChanged) {
           $checkbox.prop('checked', state).trigger('change');
         }
       });
     };
 
-    // Find all <th> with class select-all, and insert the check all checkbox.
     $table.find('th.select-all').prepend($('<input type="checkbox" class="form-checkbox" />').attr('title', strings.selectAll)).on('click', function (event) {
       if ($(event.target).is('input[type="checkbox"]')) {
-        // Loop through all checkboxes and set their state to the select all
-        // checkbox' state.
         checkboxes.each(function () {
           var $checkbox = $(this);
           var stateChanged = $checkbox.prop('checked') !== event.target.checked;
 
-          /**
-           * @checkbox {HTMLElement}
-           */
           if (stateChanged) {
             $checkbox.prop('checked', event.target.checked).trigger('change');
           }
-          // Either add or remove the selected class based on the state of the
-          // check all checkbox.
 
-          /**
-           * @checkbox {HTMLElement}
-           */
           $checkbox.closest('tr').toggleClass('selected', this.checked);
         });
-        // Update the title and the state of the check all box.
+
         updateSelectAll(event.target.checked);
       }
     });
 
-    // For each of the checkboxes within the table that are not disabled.
     checkboxes = $table.find('td input[type="checkbox"]:enabled').on('click', function (e) {
-      // Either add or remove the selected class based on the state of the
-      // check all checkbox.
-
-      /**
-       * @this {HTMLElement}
-       */
       $(this).closest('tr').toggleClass('selected', this.checked);
 
-      // If this is a shift click, we need to highlight everything in the
-      // range. Also make sure that we are actually checking checkboxes
-      // over a range and that a checkbox has been checked or unchecked before.
       if (e.shiftKey && lastChecked && lastChecked !== e.target) {
-        // We use the checkbox's parent <tr> to do our range searching.
         Drupal.tableSelectRange($(e.target).closest('tr')[0], $(lastChecked).closest('tr')[0], e.target.checked);
       }
 
-      // If all checkboxes are checked, make sure the select-all one is checked
-      // too, otherwise keep unchecked.
-      updateSelectAll((checkboxes.length === checkboxes.filter(':checked').length));
+      updateSelectAll(checkboxes.length === checkboxes.filter(':checked').length);
 
-      // Keep track of the last checked checkbox.
       lastChecked = e.target;
     });
 
-    // If all checkboxes are checked on page load, make sure the select-all one
-    // is checked too, otherwise keep unchecked.
-    updateSelectAll((checkboxes.length === checkboxes.filter(':checked').length));
+    updateSelectAll(checkboxes.length === checkboxes.filter(':checked').length);
   };
 
-  /**
-   * @param {HTMLElement} from
-   *   The HTML element representing the "from" part of the range.
-   * @param {HTMLElement} to
-   *   The HTML element representing the "to" part of the range.
-   * @param {bool} state
-   *   The state to set on the range.
-   */
   Drupal.tableSelectRange = function (from, to, state) {
-    // We determine the looping mode based on the order of from and to.
     var mode = from.rowIndex > to.rowIndex ? 'previousSibling' : 'nextSibling';
 
-    // Traverse through the sibling nodes.
     for (var i = from[mode]; i; i = i[mode]) {
       var $i;
-      // Make sure that we're only dealing with elements.
+
       if (i.nodeType !== 1) {
         continue;
       }
       $i = $(i);
-      // Either add or remove the selected class based on the state of the
-      // target checkbox.
+
       $i.toggleClass('selected', state);
       $i.find('input[type="checkbox"]').prop('checked', state);
 
       if (to.nodeType) {
-        // If we are at the end of the range, stop.
         if (i === to) {
           break;
         }
-      }
-      // A faster alternative to doing $(i).filter(to).length.
-      else if ($.filter(to, [i]).r.length) {
-        break;
-      }
+      } else if ($.filter(to, [i]).r.length) {
+          break;
+        }
     }
   };
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/misc/timezone.es6.js b/core/misc/timezone.es6.js
new file mode 100644
index 000000000000..3c88d463dd80
--- /dev/null
+++ b/core/misc/timezone.es6.js
@@ -0,0 +1,76 @@
+/**
+ * @file
+ * Timezone detection.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * Set the client's system time zone as default values of form fields.
+   *
+   * @type {Drupal~behavior}
+   */
+  Drupal.behaviors.setTimezone = {
+    attach: function (context, settings) {
+      var $timezone = $(context).find('.timezone-detect').once('timezone');
+      if ($timezone.length) {
+        var dateString = Date();
+        // In some client environments, date strings include a time zone
+        // abbreviation, between 3 and 5 letters enclosed in parentheses,
+        // which can be interpreted by PHP.
+        var matches = dateString.match(/\(([A-Z]{3,5})\)/);
+        var abbreviation = matches ? matches[1] : 0;
+
+        // For all other client environments, the abbreviation is set to "0"
+        // and the current offset from UTC and daylight saving time status are
+        // used to guess the time zone.
+        var dateNow = new Date();
+        var offsetNow = dateNow.getTimezoneOffset() * -60;
+
+        // Use January 1 and July 1 as test dates for determining daylight
+        // saving time status by comparing their offsets.
+        var dateJan = new Date(dateNow.getFullYear(), 0, 1, 12, 0, 0, 0);
+        var dateJul = new Date(dateNow.getFullYear(), 6, 1, 12, 0, 0, 0);
+        var offsetJan = dateJan.getTimezoneOffset() * -60;
+        var offsetJul = dateJul.getTimezoneOffset() * -60;
+
+        var isDaylightSavingTime;
+        // If the offset from UTC is identical on January 1 and July 1,
+        // assume daylight saving time is not used in this time zone.
+        if (offsetJan === offsetJul) {
+          isDaylightSavingTime = '';
+        }
+        // If the maximum annual offset is equivalent to the current offset,
+        // assume daylight saving time is in effect.
+        else if (Math.max(offsetJan, offsetJul) === offsetNow) {
+          isDaylightSavingTime = 1;
+        }
+        // Otherwise, assume daylight saving time is not in effect.
+        else {
+          isDaylightSavingTime = 0;
+        }
+
+        // Submit request to the system/timezone callback and set the form
+        // field to the response time zone. The client date is passed to the
+        // callback for debugging purposes. Submit a synchronous request to
+        // avoid database errors associated with concurrent requests
+        // during install.
+        var path = 'system/timezone/' + abbreviation + '/' + offsetNow + '/' + isDaylightSavingTime;
+        $.ajax({
+          async: false,
+          url: Drupal.url(path),
+          data: {date: dateString},
+          dataType: 'json',
+          success: function (data) {
+            if (data) {
+              $timezone.val(data);
+            }
+          }
+        });
+      }
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/core/misc/timezone.js b/core/misc/timezone.js
index 3c88d463dd80..cd47f08fbca8 100644
--- a/core/misc/timezone.js
+++ b/core/misc/timezone.js
@@ -1,69 +1,49 @@
 /**
- * @file
- * Timezone detection.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./misc/timezone.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   * Set the client's system time zone as default values of form fields.
-   *
-   * @type {Drupal~behavior}
-   */
   Drupal.behaviors.setTimezone = {
-    attach: function (context, settings) {
+    attach: function attach(context, settings) {
       var $timezone = $(context).find('.timezone-detect').once('timezone');
       if ($timezone.length) {
         var dateString = Date();
-        // In some client environments, date strings include a time zone
-        // abbreviation, between 3 and 5 letters enclosed in parentheses,
-        // which can be interpreted by PHP.
+
         var matches = dateString.match(/\(([A-Z]{3,5})\)/);
         var abbreviation = matches ? matches[1] : 0;
 
-        // For all other client environments, the abbreviation is set to "0"
-        // and the current offset from UTC and daylight saving time status are
-        // used to guess the time zone.
         var dateNow = new Date();
         var offsetNow = dateNow.getTimezoneOffset() * -60;
 
-        // Use January 1 and July 1 as test dates for determining daylight
-        // saving time status by comparing their offsets.
         var dateJan = new Date(dateNow.getFullYear(), 0, 1, 12, 0, 0, 0);
         var dateJul = new Date(dateNow.getFullYear(), 6, 1, 12, 0, 0, 0);
         var offsetJan = dateJan.getTimezoneOffset() * -60;
         var offsetJul = dateJul.getTimezoneOffset() * -60;
 
         var isDaylightSavingTime;
-        // If the offset from UTC is identical on January 1 and July 1,
-        // assume daylight saving time is not used in this time zone.
+
         if (offsetJan === offsetJul) {
           isDaylightSavingTime = '';
-        }
-        // If the maximum annual offset is equivalent to the current offset,
-        // assume daylight saving time is in effect.
-        else if (Math.max(offsetJan, offsetJul) === offsetNow) {
-          isDaylightSavingTime = 1;
-        }
-        // Otherwise, assume daylight saving time is not in effect.
-        else {
-          isDaylightSavingTime = 0;
-        }
+        } else if (Math.max(offsetJan, offsetJul) === offsetNow) {
+            isDaylightSavingTime = 1;
+          } else {
+              isDaylightSavingTime = 0;
+            }
 
-        // Submit request to the system/timezone callback and set the form
-        // field to the response time zone. The client date is passed to the
-        // callback for debugging purposes. Submit a synchronous request to
-        // avoid database errors associated with concurrent requests
-        // during install.
         var path = 'system/timezone/' + abbreviation + '/' + offsetNow + '/' + isDaylightSavingTime;
         $.ajax({
           async: false,
           url: Drupal.url(path),
-          data: {date: dateString},
+          data: { date: dateString },
           dataType: 'json',
-          success: function (data) {
+          success: function success(data) {
             if (data) {
               $timezone.val(data);
             }
@@ -72,5 +52,4 @@
       }
     }
   };
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/misc/vertical-tabs.es6.js b/core/misc/vertical-tabs.es6.js
new file mode 100644
index 000000000000..c7ad2fd2b44b
--- /dev/null
+++ b/core/misc/vertical-tabs.es6.js
@@ -0,0 +1,252 @@
+/**
+ * @file
+ * Define vertical tabs functionality.
+ */
+
+/**
+ * Triggers when form values inside a vertical tab changes.
+ *
+ * This is used to update the summary in vertical tabs in order to know what
+ * are the important fields' values.
+ *
+ * @event summaryUpdated
+ */
+
+(function ($, Drupal, drupalSettings) {
+
+  'use strict';
+
+  /**
+   * This script transforms a set of details into a stack of vertical tabs.
+   *
+   * Each tab may have a summary which can be updated by another
+   * script. For that to work, each details element has an associated
+   * 'verticalTabCallback' (with jQuery.data() attached to the details),
+   * which is called every time the user performs an update to a form
+   * element inside the tab pane.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches behaviors for vertical tabs.
+   */
+  Drupal.behaviors.verticalTabs = {
+    attach: function (context) {
+      var width = drupalSettings.widthBreakpoint || 640;
+      var mq = '(max-width: ' + width + 'px)';
+
+      if (window.matchMedia(mq).matches) {
+        return;
+      }
+
+      $(context).find('[data-vertical-tabs-panes]').once('vertical-tabs').each(function () {
+        var $this = $(this).addClass('vertical-tabs__panes');
+        var focusID = $this.find(':hidden.vertical-tabs__active-tab').val();
+        var tab_focus;
+
+        // Check if there are some details that can be converted to
+        // vertical-tabs.
+        var $details = $this.find('> details');
+        if ($details.length === 0) {
+          return;
+        }
+
+        // Create the tab column.
+        var tab_list = $('<ul class="vertical-tabs__menu"></ul>');
+        $this.wrap('<div class="vertical-tabs clearfix"></div>').before(tab_list);
+
+        // Transform each details into a tab.
+        $details.each(function () {
+          var $that = $(this);
+          var vertical_tab = new Drupal.verticalTab({
+            title: $that.find('> summary').text(),
+            details: $that
+          });
+          tab_list.append(vertical_tab.item);
+          $that
+            .removeClass('collapsed')
+            // prop() can't be used on browsers not supporting details element,
+            // the style won't apply to them if prop() is used.
+            .attr('open', true)
+            .addClass('vertical-tabs__pane')
+            .data('verticalTab', vertical_tab);
+          if (this.id === focusID) {
+            tab_focus = $that;
+          }
+        });
+
+        $(tab_list).find('> li').eq(0).addClass('first');
+        $(tab_list).find('> li').eq(-1).addClass('last');
+
+        if (!tab_focus) {
+          // If the current URL has a fragment and one of the tabs contains an
+          // element that matches the URL fragment, activate that tab.
+          var $locationHash = $this.find(window.location.hash);
+          if (window.location.hash && $locationHash.length) {
+            tab_focus = $locationHash.closest('.vertical-tabs__pane');
+          }
+          else {
+            tab_focus = $this.find('> .vertical-tabs__pane').eq(0);
+          }
+        }
+        if (tab_focus.length) {
+          tab_focus.data('verticalTab').focus();
+        }
+      });
+    }
+  };
+
+  /**
+   * The vertical tab object represents a single tab within a tab group.
+   *
+   * @constructor
+   *
+   * @param {object} settings
+   *   Settings object.
+   * @param {string} settings.title
+   *   The name of the tab.
+   * @param {jQuery} settings.details
+   *   The jQuery object of the details element that is the tab pane.
+   *
+   * @fires event:summaryUpdated
+   *
+   * @listens event:summaryUpdated
+   */
+  Drupal.verticalTab = function (settings) {
+    var self = this;
+    $.extend(this, settings, Drupal.theme('verticalTab', settings));
+
+    this.link.attr('href', '#' + settings.details.attr('id'));
+
+    this.link.on('click', function (e) {
+      e.preventDefault();
+      self.focus();
+    });
+
+    // Keyboard events added:
+    // Pressing the Enter key will open the tab pane.
+    this.link.on('keydown', function (event) {
+      if (event.keyCode === 13) {
+        event.preventDefault();
+        self.focus();
+        // Set focus on the first input field of the visible details/tab pane.
+        $('.vertical-tabs__pane :input:visible:enabled').eq(0).trigger('focus');
+      }
+    });
+
+    this.details
+      .on('summaryUpdated', function () {
+        self.updateSummary();
+      })
+      .trigger('summaryUpdated');
+  };
+
+  Drupal.verticalTab.prototype = {
+
+    /**
+     * Displays the tab's content pane.
+     */
+    focus: function () {
+      this.details
+        .siblings('.vertical-tabs__pane')
+        .each(function () {
+          var tab = $(this).data('verticalTab');
+          tab.details.hide();
+          tab.item.removeClass('is-selected');
+        })
+        .end()
+        .show()
+        .siblings(':hidden.vertical-tabs__active-tab')
+        .val(this.details.attr('id'));
+      this.item.addClass('is-selected');
+      // Mark the active tab for screen readers.
+      $('#active-vertical-tab').remove();
+      this.link.append('<span id="active-vertical-tab" class="visually-hidden">' + Drupal.t('(active tab)') + '</span>');
+    },
+
+    /**
+     * Updates the tab's summary.
+     */
+    updateSummary: function () {
+      this.summary.html(this.details.drupalGetSummary());
+    },
+
+    /**
+     * Shows a vertical tab pane.
+     *
+     * @return {Drupal.verticalTab}
+     *   The verticalTab instance.
+     */
+    tabShow: function () {
+      // Display the tab.
+      this.item.show();
+      // Show the vertical tabs.
+      this.item.closest('.js-form-type-vertical-tabs').show();
+      // Update .first marker for items. We need recurse from parent to retain
+      // the actual DOM element order as jQuery implements sortOrder, but not
+      // as public method.
+      this.item.parent().children('.vertical-tabs__menu-item').removeClass('first')
+        .filter(':visible').eq(0).addClass('first');
+      // Display the details element.
+      this.details.removeClass('vertical-tab--hidden').show();
+      // Focus this tab.
+      this.focus();
+      return this;
+    },
+
+    /**
+     * Hides a vertical tab pane.
+     *
+     * @return {Drupal.verticalTab}
+     *   The verticalTab instance.
+     */
+    tabHide: function () {
+      // Hide this tab.
+      this.item.hide();
+      // Update .first marker for items. We need recurse from parent to retain
+      // the actual DOM element order as jQuery implements sortOrder, but not
+      // as public method.
+      this.item.parent().children('.vertical-tabs__menu-item').removeClass('first')
+        .filter(':visible').eq(0).addClass('first');
+      // Hide the details element.
+      this.details.addClass('vertical-tab--hidden').hide();
+      // Focus the first visible tab (if there is one).
+      var $firstTab = this.details.siblings('.vertical-tabs__pane:not(.vertical-tab--hidden)').eq(0);
+      if ($firstTab.length) {
+        $firstTab.data('verticalTab').focus();
+      }
+      // Hide the vertical tabs (if no tabs remain).
+      else {
+        this.item.closest('.js-form-type-vertical-tabs').hide();
+      }
+      return this;
+    }
+  };
+
+  /**
+   * Theme function for a vertical tab.
+   *
+   * @param {object} settings
+   *   An object with the following keys:
+   * @param {string} settings.title
+   *   The name of the tab.
+   *
+   * @return {object}
+   *   This function has to return an object with at least these keys:
+   *   - item: The root tab jQuery element
+   *   - link: The anchor tag that acts as the clickable area of the tab
+   *       (jQuery version)
+   *   - summary: The jQuery element that contains the tab summary
+   */
+  Drupal.theme.verticalTab = function (settings) {
+    var tab = {};
+    tab.item = $('<li class="vertical-tabs__menu-item" tabindex="-1"></li>')
+      .append(tab.link = $('<a href="#"></a>')
+        .append(tab.title = $('<strong class="vertical-tabs__menu-item-title"></strong>').text(settings.title))
+        .append(tab.summary = $('<span class="vertical-tabs__menu-item-summary"></span>')
+        )
+      );
+    return tab;
+  };
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/misc/vertical-tabs.js b/core/misc/vertical-tabs.js
index c7ad2fd2b44b..8f6a395b4924 100644
--- a/core/misc/vertical-tabs.js
+++ b/core/misc/vertical-tabs.js
@@ -1,37 +1,17 @@
 /**
- * @file
- * Define vertical tabs functionality.
- */
-
-/**
- * Triggers when form values inside a vertical tab changes.
- *
- * This is used to update the summary in vertical tabs in order to know what
- * are the important fields' values.
- *
- * @event summaryUpdated
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./misc/vertical-tabs.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings) {
 
   'use strict';
 
-  /**
-   * This script transforms a set of details into a stack of vertical tabs.
-   *
-   * Each tab may have a summary which can be updated by another
-   * script. For that to work, each details element has an associated
-   * 'verticalTabCallback' (with jQuery.data() attached to the details),
-   * which is called every time the user performs an update to a form
-   * element inside the tab pane.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches behaviors for vertical tabs.
-   */
   Drupal.behaviors.verticalTabs = {
-    attach: function (context) {
+    attach: function attach(context) {
       var width = drupalSettings.widthBreakpoint || 640;
       var mq = '(max-width: ' + width + 'px)';
 
@@ -44,18 +24,14 @@
         var focusID = $this.find(':hidden.vertical-tabs__active-tab').val();
         var tab_focus;
 
-        // Check if there are some details that can be converted to
-        // vertical-tabs.
         var $details = $this.find('> details');
         if ($details.length === 0) {
           return;
         }
 
-        // Create the tab column.
         var tab_list = $('<ul class="vertical-tabs__menu"></ul>');
         $this.wrap('<div class="vertical-tabs clearfix"></div>').before(tab_list);
 
-        // Transform each details into a tab.
         $details.each(function () {
           var $that = $(this);
           var vertical_tab = new Drupal.verticalTab({
@@ -63,13 +39,7 @@
             details: $that
           });
           tab_list.append(vertical_tab.item);
-          $that
-            .removeClass('collapsed')
-            // prop() can't be used on browsers not supporting details element,
-            // the style won't apply to them if prop() is used.
-            .attr('open', true)
-            .addClass('vertical-tabs__pane')
-            .data('verticalTab', vertical_tab);
+          $that.removeClass('collapsed').attr('open', true).addClass('vertical-tabs__pane').data('verticalTab', vertical_tab);
           if (this.id === focusID) {
             tab_focus = $that;
           }
@@ -79,13 +49,10 @@
         $(tab_list).find('> li').eq(-1).addClass('last');
 
         if (!tab_focus) {
-          // If the current URL has a fragment and one of the tabs contains an
-          // element that matches the URL fragment, activate that tab.
           var $locationHash = $this.find(window.location.hash);
           if (window.location.hash && $locationHash.length) {
             tab_focus = $locationHash.closest('.vertical-tabs__pane');
-          }
-          else {
+          } else {
             tab_focus = $this.find('> .vertical-tabs__pane').eq(0);
           }
         }
@@ -96,22 +63,6 @@
     }
   };
 
-  /**
-   * The vertical tab object represents a single tab within a tab group.
-   *
-   * @constructor
-   *
-   * @param {object} settings
-   *   Settings object.
-   * @param {string} settings.title
-   *   The name of the tab.
-   * @param {jQuery} settings.details
-   *   The jQuery object of the details element that is the tab pane.
-   *
-   * @fires event:summaryUpdated
-   *
-   * @listens event:summaryUpdated
-   */
   Drupal.verticalTab = function (settings) {
     var self = this;
     $.extend(this, settings, Drupal.theme('verticalTab', settings));
@@ -123,130 +74,70 @@
       self.focus();
     });
 
-    // Keyboard events added:
-    // Pressing the Enter key will open the tab pane.
     this.link.on('keydown', function (event) {
       if (event.keyCode === 13) {
         event.preventDefault();
         self.focus();
-        // Set focus on the first input field of the visible details/tab pane.
+
         $('.vertical-tabs__pane :input:visible:enabled').eq(0).trigger('focus');
       }
     });
 
-    this.details
-      .on('summaryUpdated', function () {
-        self.updateSummary();
-      })
-      .trigger('summaryUpdated');
+    this.details.on('summaryUpdated', function () {
+      self.updateSummary();
+    }).trigger('summaryUpdated');
   };
 
   Drupal.verticalTab.prototype = {
-
-    /**
-     * Displays the tab's content pane.
-     */
-    focus: function () {
-      this.details
-        .siblings('.vertical-tabs__pane')
-        .each(function () {
-          var tab = $(this).data('verticalTab');
-          tab.details.hide();
-          tab.item.removeClass('is-selected');
-        })
-        .end()
-        .show()
-        .siblings(':hidden.vertical-tabs__active-tab')
-        .val(this.details.attr('id'));
+    focus: function focus() {
+      this.details.siblings('.vertical-tabs__pane').each(function () {
+        var tab = $(this).data('verticalTab');
+        tab.details.hide();
+        tab.item.removeClass('is-selected');
+      }).end().show().siblings(':hidden.vertical-tabs__active-tab').val(this.details.attr('id'));
       this.item.addClass('is-selected');
-      // Mark the active tab for screen readers.
+
       $('#active-vertical-tab').remove();
       this.link.append('<span id="active-vertical-tab" class="visually-hidden">' + Drupal.t('(active tab)') + '</span>');
     },
 
-    /**
-     * Updates the tab's summary.
-     */
-    updateSummary: function () {
+    updateSummary: function updateSummary() {
       this.summary.html(this.details.drupalGetSummary());
     },
 
-    /**
-     * Shows a vertical tab pane.
-     *
-     * @return {Drupal.verticalTab}
-     *   The verticalTab instance.
-     */
-    tabShow: function () {
-      // Display the tab.
+    tabShow: function tabShow() {
       this.item.show();
-      // Show the vertical tabs.
+
       this.item.closest('.js-form-type-vertical-tabs').show();
-      // Update .first marker for items. We need recurse from parent to retain
-      // the actual DOM element order as jQuery implements sortOrder, but not
-      // as public method.
-      this.item.parent().children('.vertical-tabs__menu-item').removeClass('first')
-        .filter(':visible').eq(0).addClass('first');
-      // Display the details element.
+
+      this.item.parent().children('.vertical-tabs__menu-item').removeClass('first').filter(':visible').eq(0).addClass('first');
+
       this.details.removeClass('vertical-tab--hidden').show();
-      // Focus this tab.
+
       this.focus();
       return this;
     },
 
-    /**
-     * Hides a vertical tab pane.
-     *
-     * @return {Drupal.verticalTab}
-     *   The verticalTab instance.
-     */
-    tabHide: function () {
-      // Hide this tab.
+    tabHide: function tabHide() {
       this.item.hide();
-      // Update .first marker for items. We need recurse from parent to retain
-      // the actual DOM element order as jQuery implements sortOrder, but not
-      // as public method.
-      this.item.parent().children('.vertical-tabs__menu-item').removeClass('first')
-        .filter(':visible').eq(0).addClass('first');
-      // Hide the details element.
+
+      this.item.parent().children('.vertical-tabs__menu-item').removeClass('first').filter(':visible').eq(0).addClass('first');
+
       this.details.addClass('vertical-tab--hidden').hide();
-      // Focus the first visible tab (if there is one).
+
       var $firstTab = this.details.siblings('.vertical-tabs__pane:not(.vertical-tab--hidden)').eq(0);
       if ($firstTab.length) {
         $firstTab.data('verticalTab').focus();
-      }
-      // Hide the vertical tabs (if no tabs remain).
-      else {
-        this.item.closest('.js-form-type-vertical-tabs').hide();
-      }
+      } else {
+          this.item.closest('.js-form-type-vertical-tabs').hide();
+        }
       return this;
     }
   };
 
-  /**
-   * Theme function for a vertical tab.
-   *
-   * @param {object} settings
-   *   An object with the following keys:
-   * @param {string} settings.title
-   *   The name of the tab.
-   *
-   * @return {object}
-   *   This function has to return an object with at least these keys:
-   *   - item: The root tab jQuery element
-   *   - link: The anchor tag that acts as the clickable area of the tab
-   *       (jQuery version)
-   *   - summary: The jQuery element that contains the tab summary
-   */
   Drupal.theme.verticalTab = function (settings) {
     var tab = {};
-    tab.item = $('<li class="vertical-tabs__menu-item" tabindex="-1"></li>')
-      .append(tab.link = $('<a href="#"></a>')
-        .append(tab.title = $('<strong class="vertical-tabs__menu-item-title"></strong>').text(settings.title))
-        .append(tab.summary = $('<span class="vertical-tabs__menu-item-summary"></span>')
-        )
-      );
+    tab.item = $('<li class="vertical-tabs__menu-item" tabindex="-1"></li>').append(tab.link = $('<a href="#"></a>').append(tab.title = $('<strong class="vertical-tabs__menu-item-title"></strong>').text(settings.title)).append(tab.summary = $('<span class="vertical-tabs__menu-item-summary"></span>')));
     return tab;
   };
-
-})(jQuery, Drupal, drupalSettings);
+})(jQuery, Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/modules/big_pipe/js/big_pipe.es6.js b/core/modules/big_pipe/js/big_pipe.es6.js
new file mode 100644
index 000000000000..cdfd766f1345
--- /dev/null
+++ b/core/modules/big_pipe/js/big_pipe.es6.js
@@ -0,0 +1,110 @@
+/**
+ * @file
+ * Renders BigPipe placeholders using Drupal's Ajax system.
+ */
+
+(function ($, Drupal, drupalSettings) {
+
+  'use strict';
+
+  /**
+   * Executes Ajax commands in <script type="application/vnd.drupal-ajax"> tag.
+   *
+   * These Ajax commands replace placeholders with HTML and load missing CSS/JS.
+   *
+   * @param {number} index
+   *   Current index.
+   * @param {HTMLScriptElement} placeholderReplacement
+   *   Script tag created by BigPipe.
+   */
+  function bigPipeProcessPlaceholderReplacement(index, placeholderReplacement) {
+    var placeholderId = placeholderReplacement.getAttribute('data-big-pipe-replacement-for-placeholder-with-id');
+    var content = this.textContent.trim();
+    // Ignore any placeholders that are not in the known placeholder list. Used
+    // to avoid someone trying to XSS the site via the placeholdering mechanism.
+    if (typeof drupalSettings.bigPipePlaceholderIds[placeholderId] !== 'undefined') {
+      // If we try to parse the content too early (when the JSON containing Ajax
+      // commands is still arriving), textContent will be empty which will cause
+      // JSON.parse() to fail. Remove once so that it can be processed again
+      // later.
+      // @see bigPipeProcessDocument()
+      if (content === '') {
+        $(this).removeOnce('big-pipe');
+      }
+      else {
+        var response = JSON.parse(content);
+        // Create a Drupal.Ajax object without associating an element, a
+        // progress indicator or a URL.
+        var ajaxObject = Drupal.ajax({
+          url: '',
+          base: false,
+          element: false,
+          progress: false
+        });
+        // Then, simulate an AJAX response having arrived, and let the Ajax
+        // system handle it.
+        ajaxObject.success(response, 'success');
+      }
+    }
+  }
+
+  /**
+   * Processes a streamed HTML document receiving placeholder replacements.
+   *
+   * @param {HTMLDocument} context
+   *   The HTML document containing <script type="application/vnd.drupal-ajax">
+   *   tags generated by BigPipe.
+   *
+   * @return {bool}
+   *   Returns true when processing has been finished and a stop signal has been
+   *   found.
+   */
+  function bigPipeProcessDocument(context) {
+    // Make sure we have BigPipe-related scripts before processing further.
+    if (!context.querySelector('script[data-big-pipe-event="start"]')) {
+      return false;
+    }
+
+    $(context).find('script[data-big-pipe-replacement-for-placeholder-with-id]')
+      .once('big-pipe')
+      .each(bigPipeProcessPlaceholderReplacement);
+
+    // If we see the stop signal, clear the timeout: all placeholder
+    // replacements are guaranteed to be received and processed.
+    if (context.querySelector('script[data-big-pipe-event="stop"]')) {
+      if (timeoutID) {
+        clearTimeout(timeoutID);
+      }
+      return true;
+    }
+
+    return false;
+  }
+
+  function bigPipeProcess() {
+    timeoutID = setTimeout(function () {
+      if (!bigPipeProcessDocument(document)) {
+        bigPipeProcess();
+      }
+    }, interval);
+  }
+
+  // The frequency with which to check for newly arrived BigPipe placeholders.
+  // Hence 50 ms means we check 20 times per second. Setting this to 100 ms or
+  // more would cause the user to see content appear noticeably slower.
+  var interval = drupalSettings.bigPipeInterval || 50;
+  // The internal ID to contain the watcher service.
+  var timeoutID;
+
+  bigPipeProcess();
+
+  // If something goes wrong, make sure everything is cleaned up and has had a
+  // chance to be processed with everything loaded.
+  $(window).on('load', function () {
+    if (timeoutID) {
+      clearTimeout(timeoutID);
+    }
+    bigPipeProcessDocument(document);
+  });
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/big_pipe/js/big_pipe.js b/core/modules/big_pipe/js/big_pipe.js
index cdfd766f1345..3e6f20a09339 100644
--- a/core/modules/big_pipe/js/big_pipe.js
+++ b/core/modules/big_pipe/js/big_pipe.js
@@ -1,76 +1,44 @@
 /**
- * @file
- * Renders BigPipe placeholders using Drupal's Ajax system.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/big_pipe/js/big_pipe.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings) {
 
   'use strict';
 
-  /**
-   * Executes Ajax commands in <script type="application/vnd.drupal-ajax"> tag.
-   *
-   * These Ajax commands replace placeholders with HTML and load missing CSS/JS.
-   *
-   * @param {number} index
-   *   Current index.
-   * @param {HTMLScriptElement} placeholderReplacement
-   *   Script tag created by BigPipe.
-   */
   function bigPipeProcessPlaceholderReplacement(index, placeholderReplacement) {
     var placeholderId = placeholderReplacement.getAttribute('data-big-pipe-replacement-for-placeholder-with-id');
     var content = this.textContent.trim();
-    // Ignore any placeholders that are not in the known placeholder list. Used
-    // to avoid someone trying to XSS the site via the placeholdering mechanism.
+
     if (typeof drupalSettings.bigPipePlaceholderIds[placeholderId] !== 'undefined') {
-      // If we try to parse the content too early (when the JSON containing Ajax
-      // commands is still arriving), textContent will be empty which will cause
-      // JSON.parse() to fail. Remove once so that it can be processed again
-      // later.
-      // @see bigPipeProcessDocument()
       if (content === '') {
         $(this).removeOnce('big-pipe');
-      }
-      else {
+      } else {
         var response = JSON.parse(content);
-        // Create a Drupal.Ajax object without associating an element, a
-        // progress indicator or a URL.
+
         var ajaxObject = Drupal.ajax({
           url: '',
           base: false,
           element: false,
           progress: false
         });
-        // Then, simulate an AJAX response having arrived, and let the Ajax
-        // system handle it.
+
         ajaxObject.success(response, 'success');
       }
     }
   }
 
-  /**
-   * Processes a streamed HTML document receiving placeholder replacements.
-   *
-   * @param {HTMLDocument} context
-   *   The HTML document containing <script type="application/vnd.drupal-ajax">
-   *   tags generated by BigPipe.
-   *
-   * @return {bool}
-   *   Returns true when processing has been finished and a stop signal has been
-   *   found.
-   */
   function bigPipeProcessDocument(context) {
-    // Make sure we have BigPipe-related scripts before processing further.
     if (!context.querySelector('script[data-big-pipe-event="start"]')) {
       return false;
     }
 
-    $(context).find('script[data-big-pipe-replacement-for-placeholder-with-id]')
-      .once('big-pipe')
-      .each(bigPipeProcessPlaceholderReplacement);
+    $(context).find('script[data-big-pipe-replacement-for-placeholder-with-id]').once('big-pipe').each(bigPipeProcessPlaceholderReplacement);
 
-    // If we see the stop signal, clear the timeout: all placeholder
-    // replacements are guaranteed to be received and processed.
     if (context.querySelector('script[data-big-pipe-event="stop"]')) {
       if (timeoutID) {
         clearTimeout(timeoutID);
@@ -89,22 +57,16 @@
     }, interval);
   }
 
-  // The frequency with which to check for newly arrived BigPipe placeholders.
-  // Hence 50 ms means we check 20 times per second. Setting this to 100 ms or
-  // more would cause the user to see content appear noticeably slower.
   var interval = drupalSettings.bigPipeInterval || 50;
-  // The internal ID to contain the watcher service.
+
   var timeoutID;
 
   bigPipeProcess();
 
-  // If something goes wrong, make sure everything is cleaned up and has had a
-  // chance to be processed with everything loaded.
   $(window).on('load', function () {
     if (timeoutID) {
       clearTimeout(timeoutID);
     }
     bigPipeProcessDocument(document);
   });
-
-})(jQuery, Drupal, drupalSettings);
+})(jQuery, Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/modules/block/js/block.admin.es6.js b/core/modules/block/js/block.admin.es6.js
new file mode 100644
index 000000000000..9f99b7fabaf0
--- /dev/null
+++ b/core/modules/block/js/block.admin.es6.js
@@ -0,0 +1,97 @@
+/**
+ * @file
+ * Block admin behaviors.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * Filters the block list by a text input search string.
+   *
+   * The text input will have the selector `input.block-filter-text`.
+   *
+   * The target element to do searching in will be in the selector
+   * `input.block-filter-text[data-element]`
+   *
+   * The text source where the text should be found will have the selector
+   * `.block-filter-text-source`
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches the behavior for the block filtering.
+   */
+  Drupal.behaviors.blockFilterByText = {
+    attach: function (context, settings) {
+      var $input = $('input.block-filter-text').once('block-filter-text');
+      var $table = $($input.attr('data-element'));
+      var $filter_rows;
+
+      /**
+       * Filters the block list.
+       *
+       * @param {jQuery.Event} e
+       *   The jQuery event for the keyup event that triggered the filter.
+       */
+      function filterBlockList(e) {
+        var query = $(e.target).val().toLowerCase();
+
+        /**
+         * Shows or hides the block entry based on the query.
+         *
+         * @param {number} index
+         *   The index in the loop, as provided by `jQuery.each`
+         * @param {HTMLElement} label
+         *   The label of the block.
+         */
+        function toggleBlockEntry(index, label) {
+          var $label = $(label);
+          var $row = $label.parent().parent();
+          var textMatch = $label.text().toLowerCase().indexOf(query) !== -1;
+          $row.toggle(textMatch);
+        }
+
+        // Filter if the length of the query is at least 2 characters.
+        if (query.length >= 2) {
+          $filter_rows.each(toggleBlockEntry);
+        }
+        else {
+          $filter_rows.each(function (index) {
+            $(this).parent().parent().show();
+          });
+        }
+      }
+
+      if ($table.length) {
+        $filter_rows = $table.find('div.block-filter-text-source');
+        $input.on('keyup', filterBlockList);
+      }
+    }
+  };
+
+  /**
+   * Highlights the block that was just placed into the block listing.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches the behavior for the block placement highlighting.
+   */
+  Drupal.behaviors.blockHighlightPlacement = {
+    attach: function (context, settings) {
+      if (settings.blockPlacement) {
+        $(context).find('[data-drupal-selector="edit-blocks"]').once('block-highlight').each(function () {
+          var $container = $(this);
+          // Just scrolling the document.body will not work in Firefox. The html
+          // element is needed as well.
+          $('html, body').animate({
+            scrollTop: $('.js-block-placed').offset().top - $container.offset().top + $container.scrollTop()
+          }, 500);
+        });
+      }
+    }
+  };
+
+}(jQuery, Drupal));
diff --git a/core/modules/block/js/block.admin.js b/core/modules/block/js/block.admin.js
index 9f99b7fabaf0..4347ab5af78f 100644
--- a/core/modules/block/js/block.admin.js
+++ b/core/modules/block/js/block.admin.js
@@ -1,51 +1,24 @@
 /**
- * @file
- * Block admin behaviors.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/block/js/block.admin.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   * Filters the block list by a text input search string.
-   *
-   * The text input will have the selector `input.block-filter-text`.
-   *
-   * The target element to do searching in will be in the selector
-   * `input.block-filter-text[data-element]`
-   *
-   * The text source where the text should be found will have the selector
-   * `.block-filter-text-source`
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches the behavior for the block filtering.
-   */
   Drupal.behaviors.blockFilterByText = {
-    attach: function (context, settings) {
+    attach: function attach(context, settings) {
       var $input = $('input.block-filter-text').once('block-filter-text');
       var $table = $($input.attr('data-element'));
       var $filter_rows;
 
-      /**
-       * Filters the block list.
-       *
-       * @param {jQuery.Event} e
-       *   The jQuery event for the keyup event that triggered the filter.
-       */
       function filterBlockList(e) {
         var query = $(e.target).val().toLowerCase();
 
-        /**
-         * Shows or hides the block entry based on the query.
-         *
-         * @param {number} index
-         *   The index in the loop, as provided by `jQuery.each`
-         * @param {HTMLElement} label
-         *   The label of the block.
-         */
         function toggleBlockEntry(index, label) {
           var $label = $(label);
           var $row = $label.parent().parent();
@@ -53,11 +26,9 @@
           $row.toggle(textMatch);
         }
 
-        // Filter if the length of the query is at least 2 characters.
         if (query.length >= 2) {
           $filter_rows.each(toggleBlockEntry);
-        }
-        else {
+        } else {
           $filter_rows.each(function (index) {
             $(this).parent().parent().show();
           });
@@ -71,21 +42,12 @@
     }
   };
 
-  /**
-   * Highlights the block that was just placed into the block listing.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches the behavior for the block placement highlighting.
-   */
   Drupal.behaviors.blockHighlightPlacement = {
-    attach: function (context, settings) {
+    attach: function attach(context, settings) {
       if (settings.blockPlacement) {
         $(context).find('[data-drupal-selector="edit-blocks"]').once('block-highlight').each(function () {
           var $container = $(this);
-          // Just scrolling the document.body will not work in Firefox. The html
-          // element is needed as well.
+
           $('html, body').animate({
             scrollTop: $('.js-block-placed').offset().top - $container.offset().top + $container.scrollTop()
           }, 500);
@@ -93,5 +55,4 @@
       }
     }
   };
-
-}(jQuery, Drupal));
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/modules/block/js/block.es6.js b/core/modules/block/js/block.es6.js
new file mode 100644
index 000000000000..f27652736559
--- /dev/null
+++ b/core/modules/block/js/block.es6.js
@@ -0,0 +1,228 @@
+/**
+ * @file
+ * Block behaviors.
+ */
+
+(function ($, window, Drupal) {
+
+  'use strict';
+
+  /**
+   * Provide the summary information for the block settings vertical tabs.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches the behavior for the block settings summaries.
+   */
+  Drupal.behaviors.blockSettingsSummary = {
+    attach: function () {
+      // The drupalSetSummary method required for this behavior is not available
+      // on the Blocks administration page, so we need to make sure this
+      // behavior is processed only if drupalSetSummary is defined.
+      if (typeof $.fn.drupalSetSummary === 'undefined') {
+        return;
+      }
+
+      /**
+       * Create a summary for checkboxes in the provided context.
+       *
+       * @param {HTMLDocument|HTMLElement} context
+       *   A context where one would find checkboxes to summarize.
+       *
+       * @return {string}
+       *   A string with the summary.
+       */
+      function checkboxesSummary(context) {
+        var vals = [];
+        var $checkboxes = $(context).find('input[type="checkbox"]:checked + label');
+        var il = $checkboxes.length;
+        for (var i = 0; i < il; i++) {
+          vals.push($($checkboxes[i]).html());
+        }
+        if (!vals.length) {
+          vals.push(Drupal.t('Not restricted'));
+        }
+        return vals.join(', ');
+      }
+
+      $('[data-drupal-selector="edit-visibility-node-type"], [data-drupal-selector="edit-visibility-language"], [data-drupal-selector="edit-visibility-user-role"]').drupalSetSummary(checkboxesSummary);
+
+      $('[data-drupal-selector="edit-visibility-request-path"]').drupalSetSummary(function (context) {
+        var $pages = $(context).find('textarea[name="visibility[request_path][pages]"]');
+        if (!$pages.val()) {
+          return Drupal.t('Not restricted');
+        }
+        else {
+          return Drupal.t('Restricted to certain pages');
+        }
+      });
+    }
+  };
+
+  /**
+   * Move a block in the blocks table between regions via select list.
+   *
+   * This behavior is dependent on the tableDrag behavior, since it uses the
+   * objects initialized in that behavior to update the row.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches the tableDrag behaviour for blocks in block administration.
+   */
+  Drupal.behaviors.blockDrag = {
+    attach: function (context, settings) {
+      // tableDrag is required and we should be on the blocks admin page.
+      if (typeof Drupal.tableDrag === 'undefined' || typeof Drupal.tableDrag.blocks === 'undefined') {
+        return;
+      }
+
+      /**
+       * Function to check empty regions and toggle classes based on this.
+       *
+       * @param {jQuery} table
+       *   The jQuery object representing the table to inspect.
+       * @param {jQuery} rowObject
+       *   The jQuery object representing the table row.
+       */
+      function checkEmptyRegions(table, rowObject) {
+        table.find('tr.region-message').each(function () {
+          var $this = $(this);
+          // If the dragged row is in this region, but above the message row,
+          // swap it down one space.
+          if ($this.prev('tr').get(0) === rowObject.element) {
+            // Prevent a recursion problem when using the keyboard to move rows
+            // up.
+            if ((rowObject.method !== 'keyboard' || rowObject.direction === 'down')) {
+              rowObject.swap('after', this);
+            }
+          }
+          // This region has become empty.
+          if ($this.next('tr').is(':not(.draggable)') || $this.next('tr').length === 0) {
+            $this.removeClass('region-populated').addClass('region-empty');
+          }
+          // This region has become populated.
+          else if ($this.is('.region-empty')) {
+            $this.removeClass('region-empty').addClass('region-populated');
+          }
+        });
+      }
+
+      /**
+       * Function to update the last placed row with the correct classes.
+       *
+       * @param {jQuery} table
+       *   The jQuery object representing the table to inspect.
+       * @param {jQuery} rowObject
+       *   The jQuery object representing the table row.
+       */
+      function updateLastPlaced(table, rowObject) {
+        // Remove the color-success class from new block if applicable.
+        table.find('.color-success').removeClass('color-success');
+
+        var $rowObject = $(rowObject);
+        if (!$rowObject.is('.drag-previous')) {
+          table.find('.drag-previous').removeClass('drag-previous');
+          $rowObject.addClass('drag-previous');
+        }
+      }
+
+      /**
+       * Update block weights in the given region.
+       *
+       * @param {jQuery} table
+       *   Table with draggable items.
+       * @param {string} region
+       *   Machine name of region containing blocks to update.
+       */
+      function updateBlockWeights(table, region) {
+        // Calculate minimum weight.
+        var weight = -Math.round(table.find('.draggable').length / 2);
+        // Update the block weights.
+        table.find('.region-' + region + '-message').nextUntil('.region-title')
+          .find('select.block-weight').val(function () {
+            // Increment the weight before assigning it to prevent using the
+            // absolute minimum available weight. This way we always have an
+            // unused upper and lower bound, which makes manually setting the
+            // weights easier for users who prefer to do it that way.
+            return ++weight;
+          });
+      }
+
+      var table = $('#blocks');
+      // Get the blocks tableDrag object.
+      var tableDrag = Drupal.tableDrag.blocks;
+      // Add a handler for when a row is swapped, update empty regions.
+      tableDrag.row.prototype.onSwap = function (swappedRow) {
+        checkEmptyRegions(table, this);
+        updateLastPlaced(table, this);
+      };
+
+      // Add a handler so when a row is dropped, update fields dropped into
+      // new regions.
+      tableDrag.onDrop = function () {
+        var dragObject = this;
+        var $rowElement = $(dragObject.rowObject.element);
+        // Use "region-message" row instead of "region" row because
+        // "region-{region_name}-message" is less prone to regexp match errors.
+        var regionRow = $rowElement.prevAll('tr.region-message').get(0);
+        var regionName = regionRow.className.replace(/([^ ]+[ ]+)*region-([^ ]+)-message([ ]+[^ ]+)*/, '$2');
+        var regionField = $rowElement.find('select.block-region-select');
+        // Check whether the newly picked region is available for this block.
+        if (regionField.find('option[value=' + regionName + ']').length === 0) {
+          // If not, alert the user and keep the block in its old region
+          // setting.
+          window.alert(Drupal.t('The block cannot be placed in this region.'));
+          // Simulate that there was a selected element change, so the row is
+          // put back to from where the user tried to drag it.
+          regionField.trigger('change');
+        }
+
+        // Update region and weight fields if the region has been changed.
+        if (!regionField.is('.block-region-' + regionName)) {
+          var weightField = $rowElement.find('select.block-weight');
+          var oldRegionName = weightField[0].className.replace(/([^ ]+[ ]+)*block-weight-([^ ]+)([ ]+[^ ]+)*/, '$2');
+          regionField.removeClass('block-region-' + oldRegionName).addClass('block-region-' + regionName);
+          weightField.removeClass('block-weight-' + oldRegionName).addClass('block-weight-' + regionName);
+          regionField.val(regionName);
+        }
+
+        updateBlockWeights(table, regionName);
+      };
+
+      // Add the behavior to each region select list.
+      $(context).find('select.block-region-select').once('block-region-select')
+        .on('change', function (event) {
+          // Make our new row and select field.
+          var row = $(this).closest('tr');
+          var select = $(this);
+          // Find the correct region and insert the row as the last in the
+          // region.
+          tableDrag.rowObject = new tableDrag.row(row[0]);
+          var region_message = table.find('.region-' + select[0].value + '-message');
+          var region_items = region_message.nextUntil('.region-message, .region-title');
+          if (region_items.length) {
+            region_items.last().after(row);
+          }
+          // We found that region_message is the last row.
+          else {
+            region_message.after(row);
+          }
+          updateBlockWeights(table, select[0].value);
+          // Modify empty regions with added or removed fields.
+          checkEmptyRegions(table, tableDrag.rowObject);
+          // Update last placed block indication.
+          updateLastPlaced(table, row);
+          // Show unsaved changes warning.
+          if (!tableDrag.changed) {
+            $(Drupal.theme('tableDragChangedWarning')).insertBefore(tableDrag.table).hide().fadeIn('slow');
+            tableDrag.changed = true;
+          }
+          // Remove focus from selectbox.
+          select.trigger('blur');
+        });
+    }
+  };
+
+})(jQuery, window, Drupal);
diff --git a/core/modules/block/js/block.js b/core/modules/block/js/block.js
index f27652736559..74d4275586e2 100644
--- a/core/modules/block/js/block.js
+++ b/core/modules/block/js/block.js
@@ -1,38 +1,21 @@
 /**
- * @file
- * Block behaviors.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/block/js/block.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, window, Drupal) {
 
   'use strict';
 
-  /**
-   * Provide the summary information for the block settings vertical tabs.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches the behavior for the block settings summaries.
-   */
   Drupal.behaviors.blockSettingsSummary = {
-    attach: function () {
-      // The drupalSetSummary method required for this behavior is not available
-      // on the Blocks administration page, so we need to make sure this
-      // behavior is processed only if drupalSetSummary is defined.
+    attach: function attach() {
       if (typeof $.fn.drupalSetSummary === 'undefined') {
         return;
       }
 
-      /**
-       * Create a summary for checkboxes in the provided context.
-       *
-       * @param {HTMLDocument|HTMLElement} context
-       *   A context where one would find checkboxes to summarize.
-       *
-       * @return {string}
-       *   A string with the summary.
-       */
       function checkboxesSummary(context) {
         var vals = [];
         var $checkboxes = $(context).find('input[type="checkbox"]:checked + label');
@@ -52,73 +35,38 @@
         var $pages = $(context).find('textarea[name="visibility[request_path][pages]"]');
         if (!$pages.val()) {
           return Drupal.t('Not restricted');
-        }
-        else {
+        } else {
           return Drupal.t('Restricted to certain pages');
         }
       });
     }
   };
 
-  /**
-   * Move a block in the blocks table between regions via select list.
-   *
-   * This behavior is dependent on the tableDrag behavior, since it uses the
-   * objects initialized in that behavior to update the row.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches the tableDrag behaviour for blocks in block administration.
-   */
   Drupal.behaviors.blockDrag = {
-    attach: function (context, settings) {
-      // tableDrag is required and we should be on the blocks admin page.
+    attach: function attach(context, settings) {
       if (typeof Drupal.tableDrag === 'undefined' || typeof Drupal.tableDrag.blocks === 'undefined') {
         return;
       }
 
-      /**
-       * Function to check empty regions and toggle classes based on this.
-       *
-       * @param {jQuery} table
-       *   The jQuery object representing the table to inspect.
-       * @param {jQuery} rowObject
-       *   The jQuery object representing the table row.
-       */
       function checkEmptyRegions(table, rowObject) {
         table.find('tr.region-message').each(function () {
           var $this = $(this);
-          // If the dragged row is in this region, but above the message row,
-          // swap it down one space.
+
           if ($this.prev('tr').get(0) === rowObject.element) {
-            // Prevent a recursion problem when using the keyboard to move rows
-            // up.
-            if ((rowObject.method !== 'keyboard' || rowObject.direction === 'down')) {
+            if (rowObject.method !== 'keyboard' || rowObject.direction === 'down') {
               rowObject.swap('after', this);
             }
           }
-          // This region has become empty.
+
           if ($this.next('tr').is(':not(.draggable)') || $this.next('tr').length === 0) {
             $this.removeClass('region-populated').addClass('region-empty');
-          }
-          // This region has become populated.
-          else if ($this.is('.region-empty')) {
-            $this.removeClass('region-empty').addClass('region-populated');
-          }
+          } else if ($this.is('.region-empty')) {
+              $this.removeClass('region-empty').addClass('region-populated');
+            }
         });
       }
 
-      /**
-       * Function to update the last placed row with the correct classes.
-       *
-       * @param {jQuery} table
-       *   The jQuery object representing the table to inspect.
-       * @param {jQuery} rowObject
-       *   The jQuery object representing the table row.
-       */
       function updateLastPlaced(table, rowObject) {
-        // Remove the color-success class from new block if applicable.
         table.find('.color-success').removeClass('color-success');
 
         var $rowObject = $(rowObject);
@@ -128,58 +76,37 @@
         }
       }
 
-      /**
-       * Update block weights in the given region.
-       *
-       * @param {jQuery} table
-       *   Table with draggable items.
-       * @param {string} region
-       *   Machine name of region containing blocks to update.
-       */
       function updateBlockWeights(table, region) {
-        // Calculate minimum weight.
         var weight = -Math.round(table.find('.draggable').length / 2);
-        // Update the block weights.
-        table.find('.region-' + region + '-message').nextUntil('.region-title')
-          .find('select.block-weight').val(function () {
-            // Increment the weight before assigning it to prevent using the
-            // absolute minimum available weight. This way we always have an
-            // unused upper and lower bound, which makes manually setting the
-            // weights easier for users who prefer to do it that way.
-            return ++weight;
-          });
+
+        table.find('.region-' + region + '-message').nextUntil('.region-title').find('select.block-weight').val(function () {
+          return ++weight;
+        });
       }
 
       var table = $('#blocks');
-      // Get the blocks tableDrag object.
+
       var tableDrag = Drupal.tableDrag.blocks;
-      // Add a handler for when a row is swapped, update empty regions.
+
       tableDrag.row.prototype.onSwap = function (swappedRow) {
         checkEmptyRegions(table, this);
         updateLastPlaced(table, this);
       };
 
-      // Add a handler so when a row is dropped, update fields dropped into
-      // new regions.
       tableDrag.onDrop = function () {
         var dragObject = this;
         var $rowElement = $(dragObject.rowObject.element);
-        // Use "region-message" row instead of "region" row because
-        // "region-{region_name}-message" is less prone to regexp match errors.
+
         var regionRow = $rowElement.prevAll('tr.region-message').get(0);
         var regionName = regionRow.className.replace(/([^ ]+[ ]+)*region-([^ ]+)-message([ ]+[^ ]+)*/, '$2');
         var regionField = $rowElement.find('select.block-region-select');
-        // Check whether the newly picked region is available for this block.
+
         if (regionField.find('option[value=' + regionName + ']').length === 0) {
-          // If not, alert the user and keep the block in its old region
-          // setting.
           window.alert(Drupal.t('The block cannot be placed in this region.'));
-          // Simulate that there was a selected element change, so the row is
-          // put back to from where the user tried to drag it.
+
           regionField.trigger('change');
         }
 
-        // Update region and weight fields if the region has been changed.
         if (!regionField.is('.block-region-' + regionName)) {
           var weightField = $rowElement.find('select.block-weight');
           var oldRegionName = weightField[0].className.replace(/([^ ]+[ ]+)*block-weight-([^ ]+)([ ]+[^ ]+)*/, '$2');
@@ -191,38 +118,31 @@
         updateBlockWeights(table, regionName);
       };
 
-      // Add the behavior to each region select list.
-      $(context).find('select.block-region-select').once('block-region-select')
-        .on('change', function (event) {
-          // Make our new row and select field.
-          var row = $(this).closest('tr');
-          var select = $(this);
-          // Find the correct region and insert the row as the last in the
-          // region.
-          tableDrag.rowObject = new tableDrag.row(row[0]);
-          var region_message = table.find('.region-' + select[0].value + '-message');
-          var region_items = region_message.nextUntil('.region-message, .region-title');
-          if (region_items.length) {
-            region_items.last().after(row);
-          }
-          // We found that region_message is the last row.
-          else {
+      $(context).find('select.block-region-select').once('block-region-select').on('change', function (event) {
+        var row = $(this).closest('tr');
+        var select = $(this);
+
+        tableDrag.rowObject = new tableDrag.row(row[0]);
+        var region_message = table.find('.region-' + select[0].value + '-message');
+        var region_items = region_message.nextUntil('.region-message, .region-title');
+        if (region_items.length) {
+          region_items.last().after(row);
+        } else {
             region_message.after(row);
           }
-          updateBlockWeights(table, select[0].value);
-          // Modify empty regions with added or removed fields.
-          checkEmptyRegions(table, tableDrag.rowObject);
-          // Update last placed block indication.
-          updateLastPlaced(table, row);
-          // Show unsaved changes warning.
-          if (!tableDrag.changed) {
-            $(Drupal.theme('tableDragChangedWarning')).insertBefore(tableDrag.table).hide().fadeIn('slow');
-            tableDrag.changed = true;
-          }
-          // Remove focus from selectbox.
-          select.trigger('blur');
-        });
+        updateBlockWeights(table, select[0].value);
+
+        checkEmptyRegions(table, tableDrag.rowObject);
+
+        updateLastPlaced(table, row);
+
+        if (!tableDrag.changed) {
+          $(Drupal.theme('tableDragChangedWarning')).insertBefore(tableDrag.table).hide().fadeIn('slow');
+          tableDrag.changed = true;
+        }
+
+        select.trigger('blur');
+      });
     }
   };
-
-})(jQuery, window, Drupal);
+})(jQuery, window, Drupal);
\ No newline at end of file
diff --git a/core/modules/book/book.es6.js b/core/modules/book/book.es6.js
new file mode 100644
index 000000000000..2cc788126479
--- /dev/null
+++ b/core/modules/book/book.es6.js
@@ -0,0 +1,37 @@
+/**
+ * @file
+ * Javascript behaviors for the Book module.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * Adds summaries to the book outline form.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches summary behavior to book outline forms.
+   */
+  Drupal.behaviors.bookDetailsSummaries = {
+    attach: function (context) {
+      $(context).find('.book-outline-form').drupalSetSummary(function (context) {
+        var $select = $(context).find('.book-title-select');
+        var val = $select.val();
+
+        if (val === '0') {
+          return Drupal.t('Not in book');
+        }
+        else if (val === 'new') {
+          return Drupal.t('New book');
+        }
+        else {
+          return Drupal.checkPlain($select.find(':selected').text());
+        }
+      });
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/core/modules/book/book.js b/core/modules/book/book.js
index 2cc788126479..3e45441e29bd 100644
--- a/core/modules/book/book.js
+++ b/core/modules/book/book.js
@@ -1,37 +1,29 @@
 /**
- * @file
- * Javascript behaviors for the Book module.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/book/book.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   * Adds summaries to the book outline form.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches summary behavior to book outline forms.
-   */
   Drupal.behaviors.bookDetailsSummaries = {
-    attach: function (context) {
+    attach: function attach(context) {
       $(context).find('.book-outline-form').drupalSetSummary(function (context) {
         var $select = $(context).find('.book-title-select');
         var val = $select.val();
 
         if (val === '0') {
           return Drupal.t('Not in book');
-        }
-        else if (val === 'new') {
+        } else if (val === 'new') {
           return Drupal.t('New book');
-        }
-        else {
+        } else {
           return Drupal.checkPlain($select.find(':selected').text());
         }
       });
     }
   };
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/modules/ckeditor/js/ckeditor.admin.es6.js b/core/modules/ckeditor/js/ckeditor.admin.es6.js
new file mode 100644
index 000000000000..11fd369586e2
--- /dev/null
+++ b/core/modules/ckeditor/js/ckeditor.admin.es6.js
@@ -0,0 +1,499 @@
+/**
+ * @file
+ * CKEditor button and group configuration user interface.
+ */
+
+(function ($, Drupal, drupalSettings, _) {
+
+  'use strict';
+
+  Drupal.ckeditor = Drupal.ckeditor || {};
+
+  /**
+   * Sets config behaviour and creates config views for the CKEditor toolbar.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches admin behaviour to the CKEditor buttons.
+   * @prop {Drupal~behaviorDetach} detach
+   *   Detaches admin behaviour from the CKEditor buttons on 'unload'.
+   */
+  Drupal.behaviors.ckeditorAdmin = {
+    attach: function (context) {
+      // Process the CKEditor configuration fragment once.
+      var $configurationForm = $(context).find('.ckeditor-toolbar-configuration').once('ckeditor-configuration');
+      if ($configurationForm.length) {
+        var $textarea = $configurationForm
+          // Hide the textarea that contains the serialized representation of the
+          // CKEditor configuration.
+          .find('.js-form-item-editor-settings-toolbar-button-groups')
+          .hide()
+          // Return the textarea child node from this expression.
+          .find('textarea');
+
+        // The HTML for the CKEditor configuration is assembled on the server
+        // and sent to the client as a serialized DOM fragment.
+        $configurationForm.append(drupalSettings.ckeditor.toolbarAdmin);
+
+        // Create a configuration model.
+        var model = Drupal.ckeditor.models.Model = new Drupal.ckeditor.Model({
+          $textarea: $textarea,
+          activeEditorConfig: JSON.parse($textarea.val()),
+          hiddenEditorConfig: drupalSettings.ckeditor.hiddenCKEditorConfig
+        });
+
+        // Create the configuration Views.
+        var viewDefaults = {
+          model: model,
+          el: $('.ckeditor-toolbar-configuration')
+        };
+        Drupal.ckeditor.views = {
+          controller: new Drupal.ckeditor.ControllerView(viewDefaults),
+          visualView: new Drupal.ckeditor.VisualView(viewDefaults),
+          keyboardView: new Drupal.ckeditor.KeyboardView(viewDefaults),
+          auralView: new Drupal.ckeditor.AuralView(viewDefaults)
+        };
+      }
+    },
+    detach: function (context, settings, trigger) {
+      // Early-return if the trigger for detachment is something else than
+      // unload.
+      if (trigger !== 'unload') {
+        return;
+      }
+
+      // We're detaching because CKEditor as text editor has been disabled; this
+      // really means that all CKEditor toolbar buttons have been removed.
+      // Hence,all editor features will be removed, so any reactions from
+      // filters will be undone.
+      var $configurationForm = $(context).find('.ckeditor-toolbar-configuration').findOnce('ckeditor-configuration');
+      if ($configurationForm.length && Drupal.ckeditor.models && Drupal.ckeditor.models.Model) {
+        var config = Drupal.ckeditor.models.Model.toJSON().activeEditorConfig;
+        var buttons = Drupal.ckeditor.views.controller.getButtonList(config);
+        var $activeToolbar = $('.ckeditor-toolbar-configuration').find('.ckeditor-toolbar-active');
+        for (var i = 0; i < buttons.length; i++) {
+          $activeToolbar.trigger('CKEditorToolbarChanged', ['removed', buttons[i]]);
+        }
+      }
+    }
+  };
+
+  /**
+   * CKEditor configuration UI methods of Backbone objects.
+   *
+   * @namespace
+   */
+  Drupal.ckeditor = {
+
+    /**
+     * A hash of View instances.
+     *
+     * @type {object}
+     */
+    views: {},
+
+    /**
+     * A hash of Model instances.
+     *
+     * @type {object}
+     */
+    models: {},
+
+    /**
+     * Translates changes in CKEditor config DOM structure to the config model.
+     *
+     * If the button is moved within an existing group, the DOM structure is
+     * simply translated to a configuration model. If the button is moved into a
+     * new group placeholder, then a process is launched to name that group
+     * before the button move is translated into configuration.
+     *
+     * @param {Backbone.View} view
+     *   The Backbone View that invoked this function.
+     * @param {jQuery} $button
+     *   A jQuery set that contains an li element that wraps a button element.
+     * @param {function} callback
+     *   A callback to invoke after the button group naming modal dialog has
+     *   been closed.
+     *
+     */
+    registerButtonMove: function (view, $button, callback) {
+      var $group = $button.closest('.ckeditor-toolbar-group');
+
+      // If dropped in a placeholder button group, the user must name it.
+      if ($group.hasClass('placeholder')) {
+        if (view.isProcessing) {
+          return;
+        }
+        view.isProcessing = true;
+
+        Drupal.ckeditor.openGroupNameDialog(view, $group, callback);
+      }
+      else {
+        view.model.set('isDirty', true);
+        callback(true);
+      }
+    },
+
+    /**
+     * Translates changes in CKEditor config DOM structure to the config model.
+     *
+     * Each row has a placeholder group at the end of the row. A user may not
+     * move an existing button group past the placeholder group at the end of a
+     * row.
+     *
+     * @param {Backbone.View} view
+     *   The Backbone View that invoked this function.
+     * @param {jQuery} $group
+     *   A jQuery set that contains an li element that wraps a group of buttons.
+     */
+    registerGroupMove: function (view, $group) {
+      // Remove placeholder classes if necessary.
+      var $row = $group.closest('.ckeditor-row');
+      if ($row.hasClass('placeholder')) {
+        $row.removeClass('placeholder');
+      }
+      // If there are any rows with just a placeholder group, mark the row as a
+      // placeholder.
+      $row.parent().children().each(function () {
+        $row = $(this);
+        if ($row.find('.ckeditor-toolbar-group').not('.placeholder').length === 0) {
+          $row.addClass('placeholder');
+        }
+      });
+      view.model.set('isDirty', true);
+    },
+
+    /**
+     * Opens a dialog with a form for changing the title of a button group.
+     *
+     * @param {Backbone.View} view
+     *   The Backbone View that invoked this function.
+     * @param {jQuery} $group
+     *   A jQuery set that contains an li element that wraps a group of buttons.
+     * @param {function} callback
+     *   A callback to invoke after the button group naming modal dialog has
+     *   been closed.
+     */
+    openGroupNameDialog: function (view, $group, callback) {
+      callback = callback || function () {};
+
+      /**
+       * Validates the string provided as a button group title.
+       *
+       * @param {HTMLElement} form
+       *   The form DOM element that contains the input with the new button
+       *   group title string.
+       *
+       * @return {bool}
+       *   Returns true when an error exists, otherwise returns false.
+       */
+      function validateForm(form) {
+        if (form.elements[0].value.length === 0) {
+          var $form = $(form);
+          if (!$form.hasClass('errors')) {
+            $form
+              .addClass('errors')
+              .find('input')
+              .addClass('error')
+              .attr('aria-invalid', 'true');
+            $('<div class=\"description\" >' + Drupal.t('Please provide a name for the button group.') + '</div>').insertAfter(form.elements[0]);
+          }
+          return true;
+        }
+        return false;
+      }
+
+      /**
+       * Attempts to close the dialog; Validates user input.
+       *
+       * @param {string} action
+       *   The dialog action chosen by the user: 'apply' or 'cancel'.
+       * @param {HTMLElement} form
+       *   The form DOM element that contains the input with the new button
+       *   group title string.
+       */
+      function closeDialog(action, form) {
+
+        /**
+         * Closes the dialog when the user cancels or supplies valid data.
+         */
+        function shutdown() {
+          dialog.close(action);
+
+          // The processing marker can be deleted since the dialog has been
+          // closed.
+          delete view.isProcessing;
+        }
+
+        /**
+         * Applies a string as the name of a CKEditor button group.
+         *
+         * @param {jQuery} $group
+         *   A jQuery set that contains an li element that wraps a group of
+         *   buttons.
+         * @param {string} name
+         *   The new name of the CKEditor button group.
+         */
+        function namePlaceholderGroup($group, name) {
+          // If it's currently still a placeholder, then that means we're
+          // creating a new group, and we must do some extra work.
+          if ($group.hasClass('placeholder')) {
+            // Remove all whitespace from the name, lowercase it and ensure
+            // HTML-safe encoding, then use this as the group ID for CKEditor
+            // configuration UI accessibility purposes only.
+            var groupID = 'ckeditor-toolbar-group-aria-label-for-' + Drupal.checkPlain(name.toLowerCase().replace(/\s/g, '-'));
+            $group
+              // Update the group container.
+              .removeAttr('aria-label')
+              .attr('data-drupal-ckeditor-type', 'group')
+              .attr('tabindex', 0)
+              // Update the group heading.
+              .children('.ckeditor-toolbar-group-name')
+              .attr('id', groupID)
+              .end()
+              // Update the group items.
+              .children('.ckeditor-toolbar-group-buttons')
+              .attr('aria-labelledby', groupID);
+          }
+
+          $group
+            .attr('data-drupal-ckeditor-toolbar-group-name', name)
+            .children('.ckeditor-toolbar-group-name')
+            .text(name);
+        }
+
+        // Invoke a user-provided callback and indicate failure.
+        if (action === 'cancel') {
+          shutdown();
+          callback(false, $group);
+          return;
+        }
+
+        // Validate that a group name was provided.
+        if (form && validateForm(form)) {
+          return;
+        }
+
+        // React to application of a valid group name.
+        if (action === 'apply') {
+          shutdown();
+          // Apply the provided name to the button group label.
+          namePlaceholderGroup($group, Drupal.checkPlain(form.elements[0].value));
+          // Remove placeholder classes so that new placeholders will be
+          // inserted.
+          $group.closest('.ckeditor-row.placeholder').addBack().removeClass('placeholder');
+
+          // Invoke a user-provided callback and indicate success.
+          callback(true, $group);
+
+          // Signal that the active toolbar DOM structure has changed.
+          view.model.set('isDirty', true);
+        }
+      }
+
+      // Create a Drupal dialog that will get a button group name from the user.
+      var $ckeditorButtonGroupNameForm = $(Drupal.theme('ckeditorButtonGroupNameForm'));
+      var dialog = Drupal.dialog($ckeditorButtonGroupNameForm.get(0), {
+        title: Drupal.t('Button group name'),
+        dialogClass: 'ckeditor-name-toolbar-group',
+        resizable: false,
+        buttons: [
+          {
+            text: Drupal.t('Apply'),
+            click: function () {
+              closeDialog('apply', this);
+            },
+            primary: true
+          },
+          {
+            text: Drupal.t('Cancel'),
+            click: function () {
+              closeDialog('cancel');
+            }
+          }
+        ],
+        open: function () {
+          var form = this;
+          var $form = $(this);
+          var $widget = $form.parent();
+          $widget.find('.ui-dialog-titlebar-close').remove();
+          // Set a click handler on the input and button in the form.
+          $widget.on('keypress.ckeditor', 'input, button', function (event) {
+            // React to enter key press.
+            if (event.keyCode === 13) {
+              var $target = $(event.currentTarget);
+              var data = $target.data('ui-button');
+              var action = 'apply';
+              // Assume 'apply', but take into account that the user might have
+              // pressed the enter key on the dialog buttons.
+              if (data && data.options && data.options.label) {
+                action = data.options.label.toLowerCase();
+              }
+              closeDialog(action, form);
+              event.stopPropagation();
+              event.stopImmediatePropagation();
+              event.preventDefault();
+            }
+          });
+          // Announce to the user that a modal dialog is open.
+          var text = Drupal.t('Editing the name of the new button group in a dialog.');
+          if (typeof $group.attr('data-drupal-ckeditor-toolbar-group-name') !== 'undefined') {
+            text = Drupal.t('Editing the name of the "@groupName" button group in a dialog.', {
+              '@groupName': $group.attr('data-drupal-ckeditor-toolbar-group-name')
+            });
+          }
+          Drupal.announce(text);
+        },
+        close: function (event) {
+          // Automatically destroy the DOM element that was used for the dialog.
+          $(event.target).remove();
+        }
+      });
+      // A modal dialog is used because the user must provide a button group
+      // name or cancel the button placement before taking any other action.
+      dialog.showModal();
+
+      $(document.querySelector('.ckeditor-name-toolbar-group').querySelector('input'))
+        // When editing, set the "group name" input in the form to the current
+        // value.
+        .attr('value', $group.attr('data-drupal-ckeditor-toolbar-group-name'))
+        // Focus on the "group name" input in the form.
+        .trigger('focus');
+    }
+
+  };
+
+  /**
+   * Automatically shows/hides settings of buttons-only CKEditor plugins.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches show/hide behaviour to Plugin Settings buttons.
+   */
+  Drupal.behaviors.ckeditorAdminButtonPluginSettings = {
+    attach: function (context) {
+      var $context = $(context);
+      var $ckeditorPluginSettings = $context.find('#ckeditor-plugin-settings').once('ckeditor-plugin-settings');
+      if ($ckeditorPluginSettings.length) {
+        // Hide all button-dependent plugin settings initially.
+        $ckeditorPluginSettings.find('[data-ckeditor-buttons]').each(function () {
+          var $this = $(this);
+          if ($this.data('verticalTab')) {
+            $this.data('verticalTab').tabHide();
+          }
+          else {
+            // On very narrow viewports, Vertical Tabs are disabled.
+            $this.hide();
+          }
+          $this.data('ckeditorButtonPluginSettingsActiveButtons', []);
+        });
+
+        // Whenever a button is added or removed, check if we should show or
+        // hide the corresponding plugin settings. (Note that upon
+        // initialization, each button that already is part of the toolbar still
+        // is considered "added", hence it also works correctly for buttons that
+        // were added previously.)
+        $context
+          .find('.ckeditor-toolbar-active')
+          .off('CKEditorToolbarChanged.ckeditorAdminPluginSettings')
+          .on('CKEditorToolbarChanged.ckeditorAdminPluginSettings', function (event, action, button) {
+            var $pluginSettings = $ckeditorPluginSettings
+              .find('[data-ckeditor-buttons~=' + button + ']');
+
+            // No settings for this button.
+            if ($pluginSettings.length === 0) {
+              return;
+            }
+
+            var verticalTab = $pluginSettings.data('verticalTab');
+            var activeButtons = $pluginSettings.data('ckeditorButtonPluginSettingsActiveButtons');
+            if (action === 'added') {
+              activeButtons.push(button);
+              // Show this plugin's settings if >=1 of its buttons are active.
+              if (verticalTab) {
+                verticalTab.tabShow();
+              }
+              else {
+                // On very narrow viewports, Vertical Tabs remain fieldsets.
+                $pluginSettings.show();
+              }
+
+            }
+            else {
+              // Remove this button from the list of active buttons.
+              activeButtons.splice(activeButtons.indexOf(button), 1);
+              // Show this plugin's settings 0 of its buttons are active.
+              if (activeButtons.length === 0) {
+                if (verticalTab) {
+                  verticalTab.tabHide();
+                }
+                else {
+                  // On very narrow viewports, Vertical Tabs are disabled.
+                  $pluginSettings.hide();
+                }
+              }
+            }
+            $pluginSettings.data('ckeditorButtonPluginSettingsActiveButtons', activeButtons);
+          });
+      }
+    }
+  };
+
+  /**
+   * Themes a blank CKEditor row.
+   *
+   * @return {string}
+   *   A HTML string for a CKEditor row.
+   */
+  Drupal.theme.ckeditorRow = function () {
+    return '<li class="ckeditor-row placeholder" role="group"><ul class="ckeditor-toolbar-groups clearfix"></ul></li>';
+  };
+
+  /**
+   * Themes a blank CKEditor button group.
+   *
+   * @return {string}
+   *   A HTML string for a CKEditor button group.
+   */
+  Drupal.theme.ckeditorToolbarGroup = function () {
+    var group = '';
+    group += '<li class="ckeditor-toolbar-group placeholder" role="presentation" aria-label="' + Drupal.t('Place a button to create a new button group.') + '">';
+    group += '<h3 class="ckeditor-toolbar-group-name">' + Drupal.t('New group') + '</h3>';
+    group += '<ul class="ckeditor-buttons ckeditor-toolbar-group-buttons" role="toolbar" data-drupal-ckeditor-button-sorting="target"></ul>';
+    group += '</li>';
+    return group;
+  };
+
+  /**
+   * Themes a form for changing the title of a CKEditor button group.
+   *
+   * @return {string}
+   *   A HTML string for the form for the title of a CKEditor button group.
+   */
+  Drupal.theme.ckeditorButtonGroupNameForm = function () {
+    return '<form><input name="group-name" required="required"></form>';
+  };
+
+  /**
+   * Themes a button that will toggle the button group names in active config.
+   *
+   * @return {string}
+   *   A HTML string for the button to toggle group names.
+   */
+  Drupal.theme.ckeditorButtonGroupNamesToggle = function () {
+    return '<button class="link ckeditor-groupnames-toggle" aria-pressed="false"></button>';
+  };
+
+  /**
+   * Themes a button that will prompt the user to name a new button group.
+   *
+   * @return {string}
+   *   A HTML string for the button to create a name for a new button group.
+   */
+  Drupal.theme.ckeditorNewButtonGroup = function () {
+    return '<li class="ckeditor-add-new-group"><button aria-label="' + Drupal.t('Add a CKEditor button group to the end of this row.') + '">' + Drupal.t('Add group') + '</button></li>';
+  };
+
+})(jQuery, Drupal, drupalSettings, _);
diff --git a/core/modules/ckeditor/js/ckeditor.admin.js b/core/modules/ckeditor/js/ckeditor.admin.js
index 11fd369586e2..1d8fea3a8fcf 100644
--- a/core/modules/ckeditor/js/ckeditor.admin.js
+++ b/core/modules/ckeditor/js/ckeditor.admin.js
@@ -1,7 +1,10 @@
 /**
- * @file
- * CKEditor button and group configuration user interface.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/ckeditor/js/ckeditor.admin.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings, _) {
 
@@ -9,41 +12,20 @@
 
   Drupal.ckeditor = Drupal.ckeditor || {};
 
-  /**
-   * Sets config behaviour and creates config views for the CKEditor toolbar.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches admin behaviour to the CKEditor buttons.
-   * @prop {Drupal~behaviorDetach} detach
-   *   Detaches admin behaviour from the CKEditor buttons on 'unload'.
-   */
   Drupal.behaviors.ckeditorAdmin = {
-    attach: function (context) {
-      // Process the CKEditor configuration fragment once.
+    attach: function attach(context) {
       var $configurationForm = $(context).find('.ckeditor-toolbar-configuration').once('ckeditor-configuration');
       if ($configurationForm.length) {
-        var $textarea = $configurationForm
-          // Hide the textarea that contains the serialized representation of the
-          // CKEditor configuration.
-          .find('.js-form-item-editor-settings-toolbar-button-groups')
-          .hide()
-          // Return the textarea child node from this expression.
-          .find('textarea');
-
-        // The HTML for the CKEditor configuration is assembled on the server
-        // and sent to the client as a serialized DOM fragment.
+        var $textarea = $configurationForm.find('.js-form-item-editor-settings-toolbar-button-groups').hide().find('textarea');
+
         $configurationForm.append(drupalSettings.ckeditor.toolbarAdmin);
 
-        // Create a configuration model.
         var model = Drupal.ckeditor.models.Model = new Drupal.ckeditor.Model({
           $textarea: $textarea,
           activeEditorConfig: JSON.parse($textarea.val()),
           hiddenEditorConfig: drupalSettings.ckeditor.hiddenCKEditorConfig
         });
 
-        // Create the configuration Views.
         var viewDefaults = {
           model: model,
           el: $('.ckeditor-toolbar-configuration')
@@ -56,17 +38,11 @@
         };
       }
     },
-    detach: function (context, settings, trigger) {
-      // Early-return if the trigger for detachment is something else than
-      // unload.
+    detach: function detach(context, settings, trigger) {
       if (trigger !== 'unload') {
         return;
       }
 
-      // We're detaching because CKEditor as text editor has been disabled; this
-      // really means that all CKEditor toolbar buttons have been removed.
-      // Hence,all editor features will be removed, so any reactions from
-      // filters will be undone.
       var $configurationForm = $(context).find('.ckeditor-toolbar-configuration').findOnce('ckeditor-configuration');
       if ($configurationForm.length && Drupal.ckeditor.models && Drupal.ckeditor.models.Model) {
         var config = Drupal.ckeditor.models.Model.toJSON().activeEditorConfig;
@@ -79,48 +55,14 @@
     }
   };
 
-  /**
-   * CKEditor configuration UI methods of Backbone objects.
-   *
-   * @namespace
-   */
   Drupal.ckeditor = {
-
-    /**
-     * A hash of View instances.
-     *
-     * @type {object}
-     */
     views: {},
 
-    /**
-     * A hash of Model instances.
-     *
-     * @type {object}
-     */
     models: {},
 
-    /**
-     * Translates changes in CKEditor config DOM structure to the config model.
-     *
-     * If the button is moved within an existing group, the DOM structure is
-     * simply translated to a configuration model. If the button is moved into a
-     * new group placeholder, then a process is launched to name that group
-     * before the button move is translated into configuration.
-     *
-     * @param {Backbone.View} view
-     *   The Backbone View that invoked this function.
-     * @param {jQuery} $button
-     *   A jQuery set that contains an li element that wraps a button element.
-     * @param {function} callback
-     *   A callback to invoke after the button group naming modal dialog has
-     *   been closed.
-     *
-     */
-    registerButtonMove: function (view, $button, callback) {
+    registerButtonMove: function registerButtonMove(view, $button, callback) {
       var $group = $button.closest('.ckeditor-toolbar-group');
 
-      // If dropped in a placeholder button group, the user must name it.
       if ($group.hasClass('placeholder')) {
         if (view.isProcessing) {
           return;
@@ -128,33 +70,18 @@
         view.isProcessing = true;
 
         Drupal.ckeditor.openGroupNameDialog(view, $group, callback);
-      }
-      else {
+      } else {
         view.model.set('isDirty', true);
         callback(true);
       }
     },
 
-    /**
-     * Translates changes in CKEditor config DOM structure to the config model.
-     *
-     * Each row has a placeholder group at the end of the row. A user may not
-     * move an existing button group past the placeholder group at the end of a
-     * row.
-     *
-     * @param {Backbone.View} view
-     *   The Backbone View that invoked this function.
-     * @param {jQuery} $group
-     *   A jQuery set that contains an li element that wraps a group of buttons.
-     */
-    registerGroupMove: function (view, $group) {
-      // Remove placeholder classes if necessary.
+    registerGroupMove: function registerGroupMove(view, $group) {
       var $row = $group.closest('.ckeditor-row');
       if ($row.hasClass('placeholder')) {
         $row.removeClass('placeholder');
       }
-      // If there are any rows with just a placeholder group, mark the row as a
-      // placeholder.
+
       $row.parent().children().each(function () {
         $row = $(this);
         if ($row.find('.ckeditor-toolbar-group').not('.placeholder').length === 0) {
@@ -164,39 +91,14 @@
       view.model.set('isDirty', true);
     },
 
-    /**
-     * Opens a dialog with a form for changing the title of a button group.
-     *
-     * @param {Backbone.View} view
-     *   The Backbone View that invoked this function.
-     * @param {jQuery} $group
-     *   A jQuery set that contains an li element that wraps a group of buttons.
-     * @param {function} callback
-     *   A callback to invoke after the button group naming modal dialog has
-     *   been closed.
-     */
-    openGroupNameDialog: function (view, $group, callback) {
+    openGroupNameDialog: function openGroupNameDialog(view, $group, callback) {
       callback = callback || function () {};
 
-      /**
-       * Validates the string provided as a button group title.
-       *
-       * @param {HTMLElement} form
-       *   The form DOM element that contains the input with the new button
-       *   group title string.
-       *
-       * @return {bool}
-       *   Returns true when an error exists, otherwise returns false.
-       */
       function validateForm(form) {
         if (form.elements[0].value.length === 0) {
           var $form = $(form);
           if (!$form.hasClass('errors')) {
-            $form
-              .addClass('errors')
-              .find('input')
-              .addClass('error')
-              .attr('aria-invalid', 'true');
+            $form.addClass('errors').find('input').addClass('error').attr('aria-invalid', 'true');
             $('<div class=\"description\" >' + Drupal.t('Please provide a name for the button group.') + '</div>').insertAfter(form.elements[0]);
           }
           return true;
@@ -204,129 +106,74 @@
         return false;
       }
 
-      /**
-       * Attempts to close the dialog; Validates user input.
-       *
-       * @param {string} action
-       *   The dialog action chosen by the user: 'apply' or 'cancel'.
-       * @param {HTMLElement} form
-       *   The form DOM element that contains the input with the new button
-       *   group title string.
-       */
       function closeDialog(action, form) {
-
-        /**
-         * Closes the dialog when the user cancels or supplies valid data.
-         */
         function shutdown() {
           dialog.close(action);
 
-          // The processing marker can be deleted since the dialog has been
-          // closed.
           delete view.isProcessing;
         }
 
-        /**
-         * Applies a string as the name of a CKEditor button group.
-         *
-         * @param {jQuery} $group
-         *   A jQuery set that contains an li element that wraps a group of
-         *   buttons.
-         * @param {string} name
-         *   The new name of the CKEditor button group.
-         */
         function namePlaceholderGroup($group, name) {
-          // If it's currently still a placeholder, then that means we're
-          // creating a new group, and we must do some extra work.
           if ($group.hasClass('placeholder')) {
-            // Remove all whitespace from the name, lowercase it and ensure
-            // HTML-safe encoding, then use this as the group ID for CKEditor
-            // configuration UI accessibility purposes only.
             var groupID = 'ckeditor-toolbar-group-aria-label-for-' + Drupal.checkPlain(name.toLowerCase().replace(/\s/g, '-'));
-            $group
-              // Update the group container.
-              .removeAttr('aria-label')
-              .attr('data-drupal-ckeditor-type', 'group')
-              .attr('tabindex', 0)
-              // Update the group heading.
-              .children('.ckeditor-toolbar-group-name')
-              .attr('id', groupID)
-              .end()
-              // Update the group items.
-              .children('.ckeditor-toolbar-group-buttons')
-              .attr('aria-labelledby', groupID);
+            $group.removeAttr('aria-label').attr('data-drupal-ckeditor-type', 'group').attr('tabindex', 0).children('.ckeditor-toolbar-group-name').attr('id', groupID).end().children('.ckeditor-toolbar-group-buttons').attr('aria-labelledby', groupID);
           }
 
-          $group
-            .attr('data-drupal-ckeditor-toolbar-group-name', name)
-            .children('.ckeditor-toolbar-group-name')
-            .text(name);
+          $group.attr('data-drupal-ckeditor-toolbar-group-name', name).children('.ckeditor-toolbar-group-name').text(name);
         }
 
-        // Invoke a user-provided callback and indicate failure.
         if (action === 'cancel') {
           shutdown();
           callback(false, $group);
           return;
         }
 
-        // Validate that a group name was provided.
         if (form && validateForm(form)) {
           return;
         }
 
-        // React to application of a valid group name.
         if (action === 'apply') {
           shutdown();
-          // Apply the provided name to the button group label.
+
           namePlaceholderGroup($group, Drupal.checkPlain(form.elements[0].value));
-          // Remove placeholder classes so that new placeholders will be
-          // inserted.
+
           $group.closest('.ckeditor-row.placeholder').addBack().removeClass('placeholder');
 
-          // Invoke a user-provided callback and indicate success.
           callback(true, $group);
 
-          // Signal that the active toolbar DOM structure has changed.
           view.model.set('isDirty', true);
         }
       }
 
-      // Create a Drupal dialog that will get a button group name from the user.
       var $ckeditorButtonGroupNameForm = $(Drupal.theme('ckeditorButtonGroupNameForm'));
       var dialog = Drupal.dialog($ckeditorButtonGroupNameForm.get(0), {
         title: Drupal.t('Button group name'),
         dialogClass: 'ckeditor-name-toolbar-group',
         resizable: false,
-        buttons: [
-          {
-            text: Drupal.t('Apply'),
-            click: function () {
-              closeDialog('apply', this);
-            },
-            primary: true
+        buttons: [{
+          text: Drupal.t('Apply'),
+          click: function click() {
+            closeDialog('apply', this);
           },
-          {
-            text: Drupal.t('Cancel'),
-            click: function () {
-              closeDialog('cancel');
-            }
+          primary: true
+        }, {
+          text: Drupal.t('Cancel'),
+          click: function click() {
+            closeDialog('cancel');
           }
-        ],
-        open: function () {
+        }],
+        open: function open() {
           var form = this;
           var $form = $(this);
           var $widget = $form.parent();
           $widget.find('.ui-dialog-titlebar-close').remove();
-          // Set a click handler on the input and button in the form.
+
           $widget.on('keypress.ckeditor', 'input, button', function (event) {
-            // React to enter key press.
             if (event.keyCode === 13) {
               var $target = $(event.currentTarget);
               var data = $target.data('ui-button');
               var action = 'apply';
-              // Assume 'apply', but take into account that the user might have
-              // pressed the enter key on the dialog buttons.
+
               if (data && data.options && data.options.label) {
                 action = data.options.label.toLowerCase();
               }
@@ -336,7 +183,7 @@
               event.preventDefault();
             }
           });
-          // Announce to the user that a modal dialog is open.
+
           var text = Drupal.t('Editing the name of the new button group in a dialog.');
           if (typeof $group.attr('data-drupal-ckeditor-toolbar-group-name') !== 'undefined') {
             text = Drupal.t('Editing the name of the "@groupName" button group in a dialog.', {
@@ -345,118 +192,71 @@
           }
           Drupal.announce(text);
         },
-        close: function (event) {
-          // Automatically destroy the DOM element that was used for the dialog.
+        close: function close(event) {
           $(event.target).remove();
         }
       });
-      // A modal dialog is used because the user must provide a button group
-      // name or cancel the button placement before taking any other action.
+
       dialog.showModal();
 
-      $(document.querySelector('.ckeditor-name-toolbar-group').querySelector('input'))
-        // When editing, set the "group name" input in the form to the current
-        // value.
-        .attr('value', $group.attr('data-drupal-ckeditor-toolbar-group-name'))
-        // Focus on the "group name" input in the form.
-        .trigger('focus');
+      $(document.querySelector('.ckeditor-name-toolbar-group').querySelector('input')).attr('value', $group.attr('data-drupal-ckeditor-toolbar-group-name')).trigger('focus');
     }
 
   };
 
-  /**
-   * Automatically shows/hides settings of buttons-only CKEditor plugins.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches show/hide behaviour to Plugin Settings buttons.
-   */
   Drupal.behaviors.ckeditorAdminButtonPluginSettings = {
-    attach: function (context) {
+    attach: function attach(context) {
       var $context = $(context);
       var $ckeditorPluginSettings = $context.find('#ckeditor-plugin-settings').once('ckeditor-plugin-settings');
       if ($ckeditorPluginSettings.length) {
-        // Hide all button-dependent plugin settings initially.
         $ckeditorPluginSettings.find('[data-ckeditor-buttons]').each(function () {
           var $this = $(this);
           if ($this.data('verticalTab')) {
             $this.data('verticalTab').tabHide();
-          }
-          else {
-            // On very narrow viewports, Vertical Tabs are disabled.
+          } else {
             $this.hide();
           }
           $this.data('ckeditorButtonPluginSettingsActiveButtons', []);
         });
 
-        // Whenever a button is added or removed, check if we should show or
-        // hide the corresponding plugin settings. (Note that upon
-        // initialization, each button that already is part of the toolbar still
-        // is considered "added", hence it also works correctly for buttons that
-        // were added previously.)
-        $context
-          .find('.ckeditor-toolbar-active')
-          .off('CKEditorToolbarChanged.ckeditorAdminPluginSettings')
-          .on('CKEditorToolbarChanged.ckeditorAdminPluginSettings', function (event, action, button) {
-            var $pluginSettings = $ckeditorPluginSettings
-              .find('[data-ckeditor-buttons~=' + button + ']');
-
-            // No settings for this button.
-            if ($pluginSettings.length === 0) {
-              return;
-            }
+        $context.find('.ckeditor-toolbar-active').off('CKEditorToolbarChanged.ckeditorAdminPluginSettings').on('CKEditorToolbarChanged.ckeditorAdminPluginSettings', function (event, action, button) {
+          var $pluginSettings = $ckeditorPluginSettings.find('[data-ckeditor-buttons~=' + button + ']');
 
-            var verticalTab = $pluginSettings.data('verticalTab');
-            var activeButtons = $pluginSettings.data('ckeditorButtonPluginSettingsActiveButtons');
-            if (action === 'added') {
-              activeButtons.push(button);
-              // Show this plugin's settings if >=1 of its buttons are active.
-              if (verticalTab) {
-                verticalTab.tabShow();
-              }
-              else {
-                // On very narrow viewports, Vertical Tabs remain fieldsets.
-                $pluginSettings.show();
-              }
+          if ($pluginSettings.length === 0) {
+            return;
+          }
+
+          var verticalTab = $pluginSettings.data('verticalTab');
+          var activeButtons = $pluginSettings.data('ckeditorButtonPluginSettingsActiveButtons');
+          if (action === 'added') {
+            activeButtons.push(button);
 
+            if (verticalTab) {
+              verticalTab.tabShow();
+            } else {
+              $pluginSettings.show();
             }
-            else {
-              // Remove this button from the list of active buttons.
-              activeButtons.splice(activeButtons.indexOf(button), 1);
-              // Show this plugin's settings 0 of its buttons are active.
-              if (activeButtons.length === 0) {
-                if (verticalTab) {
-                  verticalTab.tabHide();
-                }
-                else {
-                  // On very narrow viewports, Vertical Tabs are disabled.
-                  $pluginSettings.hide();
-                }
+          } else {
+            activeButtons.splice(activeButtons.indexOf(button), 1);
+
+            if (activeButtons.length === 0) {
+              if (verticalTab) {
+                verticalTab.tabHide();
+              } else {
+                $pluginSettings.hide();
               }
             }
-            $pluginSettings.data('ckeditorButtonPluginSettingsActiveButtons', activeButtons);
-          });
+          }
+          $pluginSettings.data('ckeditorButtonPluginSettingsActiveButtons', activeButtons);
+        });
       }
     }
   };
 
-  /**
-   * Themes a blank CKEditor row.
-   *
-   * @return {string}
-   *   A HTML string for a CKEditor row.
-   */
   Drupal.theme.ckeditorRow = function () {
     return '<li class="ckeditor-row placeholder" role="group"><ul class="ckeditor-toolbar-groups clearfix"></ul></li>';
   };
 
-  /**
-   * Themes a blank CKEditor button group.
-   *
-   * @return {string}
-   *   A HTML string for a CKEditor button group.
-   */
   Drupal.theme.ckeditorToolbarGroup = function () {
     var group = '';
     group += '<li class="ckeditor-toolbar-group placeholder" role="presentation" aria-label="' + Drupal.t('Place a button to create a new button group.') + '">';
@@ -466,34 +266,15 @@
     return group;
   };
 
-  /**
-   * Themes a form for changing the title of a CKEditor button group.
-   *
-   * @return {string}
-   *   A HTML string for the form for the title of a CKEditor button group.
-   */
   Drupal.theme.ckeditorButtonGroupNameForm = function () {
     return '<form><input name="group-name" required="required"></form>';
   };
 
-  /**
-   * Themes a button that will toggle the button group names in active config.
-   *
-   * @return {string}
-   *   A HTML string for the button to toggle group names.
-   */
   Drupal.theme.ckeditorButtonGroupNamesToggle = function () {
     return '<button class="link ckeditor-groupnames-toggle" aria-pressed="false"></button>';
   };
 
-  /**
-   * Themes a button that will prompt the user to name a new button group.
-   *
-   * @return {string}
-   *   A HTML string for the button to create a name for a new button group.
-   */
   Drupal.theme.ckeditorNewButtonGroup = function () {
     return '<li class="ckeditor-add-new-group"><button aria-label="' + Drupal.t('Add a CKEditor button group to the end of this row.') + '">' + Drupal.t('Add group') + '</button></li>';
   };
-
-})(jQuery, Drupal, drupalSettings, _);
+})(jQuery, Drupal, drupalSettings, _);
\ No newline at end of file
diff --git a/core/modules/ckeditor/js/ckeditor.drupalimage.admin.es6.js b/core/modules/ckeditor/js/ckeditor.drupalimage.admin.es6.js
new file mode 100644
index 000000000000..256bb41c02b4
--- /dev/null
+++ b/core/modules/ckeditor/js/ckeditor.drupalimage.admin.es6.js
@@ -0,0 +1,45 @@
+/**
+ * @file
+ * CKEditor 'drupalimage' plugin admin behavior.
+ */
+
+(function ($, Drupal, drupalSettings) {
+
+  'use strict';
+
+  /**
+   * Provides the summary for the "drupalimage" plugin settings vertical tab.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches summary behaviour to the "drupalimage" settings vertical tab.
+   */
+  Drupal.behaviors.ckeditorDrupalImageSettingsSummary = {
+    attach: function () {
+      $('[data-ckeditor-plugin-id="drupalimage"]').drupalSetSummary(function (context) {
+        var root = 'input[name="editor[settings][plugins][drupalimage][image_upload]';
+        var $status = $(root + '[status]"]');
+        var $maxFileSize = $(root + '[max_size]"]');
+        var $maxWidth = $(root + '[max_dimensions][width]"]');
+        var $maxHeight = $(root + '[max_dimensions][height]"]');
+        var $scheme = $(root + '[scheme]"]:checked');
+
+        var maxFileSize = $maxFileSize.val() ? $maxFileSize.val() : $maxFileSize.attr('placeholder');
+        var maxDimensions = ($maxWidth.val() && $maxHeight.val()) ? '(' + $maxWidth.val() + 'x' + $maxHeight.val() + ')' : '';
+
+        if (!$status.is(':checked')) {
+          return Drupal.t('Uploads disabled');
+        }
+
+        var output = '';
+        output += Drupal.t('Uploads enabled, max size: @size @dimensions', {'@size': maxFileSize, '@dimensions': maxDimensions});
+        if ($scheme.length) {
+          output += '<br />' + $scheme.attr('data-label');
+        }
+        return output;
+      });
+    }
+  };
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/ckeditor/js/ckeditor.drupalimage.admin.js b/core/modules/ckeditor/js/ckeditor.drupalimage.admin.js
index 256bb41c02b4..3f568178a5e6 100644
--- a/core/modules/ckeditor/js/ckeditor.drupalimage.admin.js
+++ b/core/modules/ckeditor/js/ckeditor.drupalimage.admin.js
@@ -1,22 +1,17 @@
 /**
- * @file
- * CKEditor 'drupalimage' plugin admin behavior.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/ckeditor/js/ckeditor.drupalimage.admin.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings) {
 
   'use strict';
 
-  /**
-   * Provides the summary for the "drupalimage" plugin settings vertical tab.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches summary behaviour to the "drupalimage" settings vertical tab.
-   */
   Drupal.behaviors.ckeditorDrupalImageSettingsSummary = {
-    attach: function () {
+    attach: function attach() {
       $('[data-ckeditor-plugin-id="drupalimage"]').drupalSetSummary(function (context) {
         var root = 'input[name="editor[settings][plugins][drupalimage][image_upload]';
         var $status = $(root + '[status]"]');
@@ -26,14 +21,14 @@
         var $scheme = $(root + '[scheme]"]:checked');
 
         var maxFileSize = $maxFileSize.val() ? $maxFileSize.val() : $maxFileSize.attr('placeholder');
-        var maxDimensions = ($maxWidth.val() && $maxHeight.val()) ? '(' + $maxWidth.val() + 'x' + $maxHeight.val() + ')' : '';
+        var maxDimensions = $maxWidth.val() && $maxHeight.val() ? '(' + $maxWidth.val() + 'x' + $maxHeight.val() + ')' : '';
 
         if (!$status.is(':checked')) {
           return Drupal.t('Uploads disabled');
         }
 
         var output = '';
-        output += Drupal.t('Uploads enabled, max size: @size @dimensions', {'@size': maxFileSize, '@dimensions': maxDimensions});
+        output += Drupal.t('Uploads enabled, max size: @size @dimensions', { '@size': maxFileSize, '@dimensions': maxDimensions });
         if ($scheme.length) {
           output += '<br />' + $scheme.attr('data-label');
         }
@@ -41,5 +36,4 @@
       });
     }
   };
-
-})(jQuery, Drupal, drupalSettings);
+})(jQuery, Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/modules/ckeditor/js/ckeditor.es6.js b/core/modules/ckeditor/js/ckeditor.es6.js
new file mode 100644
index 000000000000..2b469c8037c7
--- /dev/null
+++ b/core/modules/ckeditor/js/ckeditor.es6.js
@@ -0,0 +1,350 @@
+/**
+ * @file
+ * CKEditor implementation of {@link Drupal.editors} API.
+ */
+
+(function (Drupal, debounce, CKEDITOR, $, displace, AjaxCommands) {
+
+  'use strict';
+
+  /**
+   * @namespace
+   */
+  Drupal.editors.ckeditor = {
+
+    /**
+     * Editor attach callback.
+     *
+     * @param {HTMLElement} element
+     *   The element to attach the editor to.
+     * @param {string} format
+     *   The text format for the editor.
+     *
+     * @return {bool}
+     *   Whether the call to `CKEDITOR.replace()` created an editor or not.
+     */
+    attach: function (element, format) {
+      this._loadExternalPlugins(format);
+      // Also pass settings that are Drupal-specific.
+      format.editorSettings.drupal = {
+        format: format.format
+      };
+
+      // Set a title on the CKEditor instance that includes the text field's
+      // label so that screen readers say something that is understandable
+      // for end users.
+      var label = $('label[for=' + element.getAttribute('id') + ']').html();
+      format.editorSettings.title = Drupal.t('Rich Text Editor, !label field', {'!label': label});
+
+      return !!CKEDITOR.replace(element, format.editorSettings);
+    },
+
+    /**
+     * Editor detach callback.
+     *
+     * @param {HTMLElement} element
+     *   The element to detach the editor from.
+     * @param {string} format
+     *   The text format used for the editor.
+     * @param {string} trigger
+     *   The event trigger for the detach.
+     *
+     * @return {bool}
+     *   Whether the call to `CKEDITOR.dom.element.get(element).getEditor()`
+     *   found an editor or not.
+     */
+    detach: function (element, format, trigger) {
+      var editor = CKEDITOR.dom.element.get(element).getEditor();
+      if (editor) {
+        if (trigger === 'serialize') {
+          editor.updateElement();
+        }
+        else {
+          editor.destroy();
+          element.removeAttribute('contentEditable');
+        }
+      }
+      return !!editor;
+    },
+
+    /**
+     * Reacts on a change in the editor element.
+     *
+     * @param {HTMLElement} element
+     *   The element where the change occured.
+     * @param {function} callback
+     *   Callback called with the value of the editor.
+     *
+     * @return {bool}
+     *   Whether the call to `CKEDITOR.dom.element.get(element).getEditor()`
+     *   found an editor or not.
+     */
+    onChange: function (element, callback) {
+      var editor = CKEDITOR.dom.element.get(element).getEditor();
+      if (editor) {
+        editor.on('change', debounce(function () {
+          callback(editor.getData());
+        }, 400));
+
+        // A temporary workaround to control scrollbar appearance when using
+        // autoGrow event to control editor's height.
+        // @todo Remove when http://dev.ckeditor.com/ticket/12120 is fixed.
+        editor.on('mode', function () {
+          var editable = editor.editable();
+          if (!editable.isInline()) {
+            editor.on('autoGrow', function (evt) {
+              var doc = evt.editor.document;
+              var scrollable = CKEDITOR.env.quirks ? doc.getBody() : doc.getDocumentElement();
+
+              if (scrollable.$.scrollHeight < scrollable.$.clientHeight) {
+                scrollable.setStyle('overflow-y', 'hidden');
+              }
+              else {
+                scrollable.removeStyle('overflow-y');
+              }
+            }, null, null, 10000);
+          }
+        });
+      }
+      return !!editor;
+    },
+
+    /**
+     * Attaches an inline editor to a DOM element.
+     *
+     * @param {HTMLElement} element
+     *   The element to attach the editor to.
+     * @param {object} format
+     *   The text format used in the editor.
+     * @param {string} [mainToolbarId]
+     *   The id attribute for the main editor toolbar, if any.
+     * @param {string} [floatedToolbarId]
+     *   The id attribute for the floated editor toolbar, if any.
+     *
+     * @return {bool}
+     *   Whether the call to `CKEDITOR.replace()` created an editor or not.
+     */
+    attachInlineEditor: function (element, format, mainToolbarId, floatedToolbarId) {
+      this._loadExternalPlugins(format);
+      // Also pass settings that are Drupal-specific.
+      format.editorSettings.drupal = {
+        format: format.format
+      };
+
+      var settings = $.extend(true, {}, format.editorSettings);
+
+      // If a toolbar is already provided for "true WYSIWYG" (in-place editing),
+      // then use that toolbar instead: override the default settings to render
+      // CKEditor UI's top toolbar into mainToolbar, and don't render the bottom
+      // toolbar at all. (CKEditor doesn't need a floated toolbar.)
+      if (mainToolbarId) {
+        var settingsOverride = {
+          extraPlugins: 'sharedspace',
+          removePlugins: 'floatingspace,elementspath',
+          sharedSpaces: {
+            top: mainToolbarId
+          }
+        };
+
+        // Find the "Source" button, if any, and replace it with "Sourcedialog".
+        // (The 'sourcearea' plugin only works in CKEditor's iframe mode.)
+        var sourceButtonFound = false;
+        for (var i = 0; !sourceButtonFound && i < settings.toolbar.length; i++) {
+          if (settings.toolbar[i] !== '/') {
+            for (var j = 0; !sourceButtonFound && j < settings.toolbar[i].items.length; j++) {
+              if (settings.toolbar[i].items[j] === 'Source') {
+                sourceButtonFound = true;
+                // Swap sourcearea's "Source" button for sourcedialog's.
+                settings.toolbar[i].items[j] = 'Sourcedialog';
+                settingsOverride.extraPlugins += ',sourcedialog';
+                settingsOverride.removePlugins += ',sourcearea';
+              }
+            }
+          }
+        }
+
+        settings.extraPlugins += ',' + settingsOverride.extraPlugins;
+        settings.removePlugins += ',' + settingsOverride.removePlugins;
+        settings.sharedSpaces = settingsOverride.sharedSpaces;
+      }
+
+      // CKEditor requires an element to already have the contentEditable
+      // attribute set to "true", otherwise it won't attach an inline editor.
+      element.setAttribute('contentEditable', 'true');
+
+      return !!CKEDITOR.inline(element, settings);
+    },
+
+    /**
+     * Loads the required external plugins for the editor.
+     *
+     * @param {object} format
+     *   The text format used in the editor.
+     */
+    _loadExternalPlugins: function (format) {
+      var externalPlugins = format.editorSettings.drupalExternalPlugins;
+      // Register and load additional CKEditor plugins as necessary.
+      if (externalPlugins) {
+        for (var pluginName in externalPlugins) {
+          if (externalPlugins.hasOwnProperty(pluginName)) {
+            CKEDITOR.plugins.addExternal(pluginName, externalPlugins[pluginName], '');
+          }
+        }
+        delete format.editorSettings.drupalExternalPlugins;
+      }
+    }
+
+  };
+
+  Drupal.ckeditor = {
+
+    /**
+     * Variable storing the current dialog's save callback.
+     *
+     * @type {?function}
+     */
+    saveCallback: null,
+
+    /**
+     * Open a dialog for a Drupal-based plugin.
+     *
+     * This dynamically loads jQuery UI (if necessary) using the Drupal AJAX
+     * framework, then opens a dialog at the specified Drupal path.
+     *
+     * @param {CKEditor} editor
+     *   The CKEditor instance that is opening the dialog.
+     * @param {string} url
+     *   The URL that contains the contents of the dialog.
+     * @param {object} existingValues
+     *   Existing values that will be sent via POST to the url for the dialog
+     *   contents.
+     * @param {function} saveCallback
+     *   A function to be called upon saving the dialog.
+     * @param {object} dialogSettings
+     *   An object containing settings to be passed to the jQuery UI.
+     */
+    openDialog: function (editor, url, existingValues, saveCallback, dialogSettings) {
+      // Locate a suitable place to display our loading indicator.
+      var $target = $(editor.container.$);
+      if (editor.elementMode === CKEDITOR.ELEMENT_MODE_REPLACE) {
+        $target = $target.find('.cke_contents');
+      }
+
+      // Remove any previous loading indicator.
+      $target.css('position', 'relative').find('.ckeditor-dialog-loading').remove();
+
+      // Add a consistent dialog class.
+      var classes = dialogSettings.dialogClass ? dialogSettings.dialogClass.split(' ') : [];
+      classes.push('ui-dialog--narrow');
+      dialogSettings.dialogClass = classes.join(' ');
+      dialogSettings.autoResize = window.matchMedia('(min-width: 600px)').matches;
+      dialogSettings.width = 'auto';
+
+      // Add a "Loading…" message, hide it underneath the CKEditor toolbar,
+      // create a Drupal.Ajax instance to load the dialog and trigger it.
+      var $content = $('<div class="ckeditor-dialog-loading"><span style="top: -40px;" class="ckeditor-dialog-loading-link">' + Drupal.t('Loading...') + '</span></div>');
+      $content.appendTo($target);
+
+      var ckeditorAjaxDialog = Drupal.ajax({
+        dialog: dialogSettings,
+        dialogType: 'modal',
+        selector: '.ckeditor-dialog-loading-link',
+        url: url,
+        progress: {type: 'throbber'},
+        submit: {
+          editor_object: existingValues
+        }
+      });
+      ckeditorAjaxDialog.execute();
+
+      // After a short delay, show "Loading…" message.
+      window.setTimeout(function () {
+        $content.find('span').animate({top: '0px'});
+      }, 1000);
+
+      // Store the save callback to be executed when this dialog is closed.
+      Drupal.ckeditor.saveCallback = saveCallback;
+    }
+  };
+
+  // Moves the dialog to the top of the CKEDITOR stack.
+  $(window).on('dialogcreate', function (e, dialog, $element, settings) {
+    $('.ui-dialog--narrow').css('zIndex', CKEDITOR.config.baseFloatZIndex + 1);
+  });
+
+  // Respond to new dialogs that are opened by CKEditor, closing the AJAX loader.
+  $(window).on('dialog:beforecreate', function (e, dialog, $element, settings) {
+    $('.ckeditor-dialog-loading').animate({top: '-40px'}, function () {
+      $(this).remove();
+    });
+  });
+
+  // Respond to dialogs that are saved, sending data back to CKEditor.
+  $(window).on('editor:dialogsave', function (e, values) {
+    if (Drupal.ckeditor.saveCallback) {
+      Drupal.ckeditor.saveCallback(values);
+    }
+  });
+
+  // Respond to dialogs that are closed, removing the current save handler.
+  $(window).on('dialog:afterclose', function (e, dialog, $element) {
+    if (Drupal.ckeditor.saveCallback) {
+      Drupal.ckeditor.saveCallback = null;
+    }
+  });
+
+  // Formulate a default formula for the maximum autoGrow height.
+  $(document).on('drupalViewportOffsetChange', function () {
+    CKEDITOR.config.autoGrow_maxHeight = 0.7 * (window.innerHeight - displace.offsets.top - displace.offsets.bottom);
+  });
+
+  // Redirect on hash change when the original hash has an associated CKEditor.
+  function redirectTextareaFragmentToCKEditorInstance() {
+    var hash = location.hash.substr(1);
+    var element = document.getElementById(hash);
+    if (element) {
+      var editor = CKEDITOR.dom.element.get(element).getEditor();
+      if (editor) {
+        var id = editor.container.getAttribute('id');
+        location.replace('#' + id);
+      }
+    }
+  }
+  $(window).on('hashchange.ckeditor', redirectTextareaFragmentToCKEditorInstance);
+
+  // Set autoGrow to make the editor grow the moment it is created.
+  CKEDITOR.config.autoGrow_onStartup = true;
+
+  // Set the CKEditor cache-busting string to the same value as Drupal.
+  CKEDITOR.timestamp = drupalSettings.ckeditor.timestamp;
+
+  if (AjaxCommands) {
+
+    /**
+     * Command to add style sheets to a CKEditor instance.
+     *
+     * Works for both iframe and inline CKEditor instances.
+     *
+     * @param {Drupal.Ajax} [ajax]
+     *   {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
+     * @param {object} response
+     *   The response from the Ajax request.
+     * @param {string} response.editor_id
+     *   The CKEditor instance ID.
+     * @param {number} [status]
+     *   The XMLHttpRequest status.
+     *
+     * @see http://docs.ckeditor.com/#!/api/CKEDITOR.dom.document
+     */
+    AjaxCommands.prototype.ckeditor_add_stylesheet = function (ajax, response, status) {
+      var editor = CKEDITOR.instances[response.editor_id];
+
+      if (editor) {
+        response.stylesheets.forEach(function (url) {
+          editor.document.appendStyleSheet(url);
+        });
+      }
+    };
+  }
+
+})(Drupal, Drupal.debounce, CKEDITOR, jQuery, Drupal.displace, Drupal.AjaxCommands);
diff --git a/core/modules/ckeditor/js/ckeditor.js b/core/modules/ckeditor/js/ckeditor.js
index 2b469c8037c7..bf130cfd6241 100644
--- a/core/modules/ckeditor/js/ckeditor.js
+++ b/core/modules/ckeditor/js/ckeditor.js
@@ -1,65 +1,35 @@
 /**
- * @file
- * CKEditor implementation of {@link Drupal.editors} API.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/ckeditor/js/ckeditor.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function (Drupal, debounce, CKEDITOR, $, displace, AjaxCommands) {
 
   'use strict';
 
-  /**
-   * @namespace
-   */
   Drupal.editors.ckeditor = {
-
-    /**
-     * Editor attach callback.
-     *
-     * @param {HTMLElement} element
-     *   The element to attach the editor to.
-     * @param {string} format
-     *   The text format for the editor.
-     *
-     * @return {bool}
-     *   Whether the call to `CKEDITOR.replace()` created an editor or not.
-     */
-    attach: function (element, format) {
+    attach: function attach(element, format) {
       this._loadExternalPlugins(format);
-      // Also pass settings that are Drupal-specific.
+
       format.editorSettings.drupal = {
         format: format.format
       };
 
-      // Set a title on the CKEditor instance that includes the text field's
-      // label so that screen readers say something that is understandable
-      // for end users.
       var label = $('label[for=' + element.getAttribute('id') + ']').html();
-      format.editorSettings.title = Drupal.t('Rich Text Editor, !label field', {'!label': label});
+      format.editorSettings.title = Drupal.t('Rich Text Editor, !label field', { '!label': label });
 
       return !!CKEDITOR.replace(element, format.editorSettings);
     },
 
-    /**
-     * Editor detach callback.
-     *
-     * @param {HTMLElement} element
-     *   The element to detach the editor from.
-     * @param {string} format
-     *   The text format used for the editor.
-     * @param {string} trigger
-     *   The event trigger for the detach.
-     *
-     * @return {bool}
-     *   Whether the call to `CKEDITOR.dom.element.get(element).getEditor()`
-     *   found an editor or not.
-     */
-    detach: function (element, format, trigger) {
+    detach: function detach(element, format, trigger) {
       var editor = CKEDITOR.dom.element.get(element).getEditor();
       if (editor) {
         if (trigger === 'serialize') {
           editor.updateElement();
-        }
-        else {
+        } else {
           editor.destroy();
           element.removeAttribute('contentEditable');
         }
@@ -67,28 +37,13 @@
       return !!editor;
     },
 
-    /**
-     * Reacts on a change in the editor element.
-     *
-     * @param {HTMLElement} element
-     *   The element where the change occured.
-     * @param {function} callback
-     *   Callback called with the value of the editor.
-     *
-     * @return {bool}
-     *   Whether the call to `CKEDITOR.dom.element.get(element).getEditor()`
-     *   found an editor or not.
-     */
-    onChange: function (element, callback) {
+    onChange: function onChange(element, callback) {
       var editor = CKEDITOR.dom.element.get(element).getEditor();
       if (editor) {
         editor.on('change', debounce(function () {
           callback(editor.getData());
         }, 400));
 
-        // A temporary workaround to control scrollbar appearance when using
-        // autoGrow event to control editor's height.
-        // @todo Remove when http://dev.ckeditor.com/ticket/12120 is fixed.
         editor.on('mode', function () {
           var editable = editor.editable();
           if (!editable.isInline()) {
@@ -98,8 +53,7 @@
 
               if (scrollable.$.scrollHeight < scrollable.$.clientHeight) {
                 scrollable.setStyle('overflow-y', 'hidden');
-              }
-              else {
+              } else {
                 scrollable.removeStyle('overflow-y');
               }
             }, null, null, 10000);
@@ -109,34 +63,15 @@
       return !!editor;
     },
 
-    /**
-     * Attaches an inline editor to a DOM element.
-     *
-     * @param {HTMLElement} element
-     *   The element to attach the editor to.
-     * @param {object} format
-     *   The text format used in the editor.
-     * @param {string} [mainToolbarId]
-     *   The id attribute for the main editor toolbar, if any.
-     * @param {string} [floatedToolbarId]
-     *   The id attribute for the floated editor toolbar, if any.
-     *
-     * @return {bool}
-     *   Whether the call to `CKEDITOR.replace()` created an editor or not.
-     */
-    attachInlineEditor: function (element, format, mainToolbarId, floatedToolbarId) {
+    attachInlineEditor: function attachInlineEditor(element, format, mainToolbarId, floatedToolbarId) {
       this._loadExternalPlugins(format);
-      // Also pass settings that are Drupal-specific.
+
       format.editorSettings.drupal = {
         format: format.format
       };
 
       var settings = $.extend(true, {}, format.editorSettings);
 
-      // If a toolbar is already provided for "true WYSIWYG" (in-place editing),
-      // then use that toolbar instead: override the default settings to render
-      // CKEditor UI's top toolbar into mainToolbar, and don't render the bottom
-      // toolbar at all. (CKEditor doesn't need a floated toolbar.)
       if (mainToolbarId) {
         var settingsOverride = {
           extraPlugins: 'sharedspace',
@@ -146,15 +81,13 @@
           }
         };
 
-        // Find the "Source" button, if any, and replace it with "Sourcedialog".
-        // (The 'sourcearea' plugin only works in CKEditor's iframe mode.)
         var sourceButtonFound = false;
         for (var i = 0; !sourceButtonFound && i < settings.toolbar.length; i++) {
           if (settings.toolbar[i] !== '/') {
             for (var j = 0; !sourceButtonFound && j < settings.toolbar[i].items.length; j++) {
               if (settings.toolbar[i].items[j] === 'Source') {
                 sourceButtonFound = true;
-                // Swap sourcearea's "Source" button for sourcedialog's.
+
                 settings.toolbar[i].items[j] = 'Sourcedialog';
                 settingsOverride.extraPlugins += ',sourcedialog';
                 settingsOverride.removePlugins += ',sourcearea';
@@ -168,22 +101,14 @@
         settings.sharedSpaces = settingsOverride.sharedSpaces;
       }
 
-      // CKEditor requires an element to already have the contentEditable
-      // attribute set to "true", otherwise it won't attach an inline editor.
       element.setAttribute('contentEditable', 'true');
 
       return !!CKEDITOR.inline(element, settings);
     },
 
-    /**
-     * Loads the required external plugins for the editor.
-     *
-     * @param {object} format
-     *   The text format used in the editor.
-     */
-    _loadExternalPlugins: function (format) {
+    _loadExternalPlugins: function _loadExternalPlugins(format) {
       var externalPlugins = format.editorSettings.drupalExternalPlugins;
-      // Register and load additional CKEditor plugins as necessary.
+
       if (externalPlugins) {
         for (var pluginName in externalPlugins) {
           if (externalPlugins.hasOwnProperty(pluginName)) {
@@ -197,51 +122,22 @@
   };
 
   Drupal.ckeditor = {
-
-    /**
-     * Variable storing the current dialog's save callback.
-     *
-     * @type {?function}
-     */
     saveCallback: null,
 
-    /**
-     * Open a dialog for a Drupal-based plugin.
-     *
-     * This dynamically loads jQuery UI (if necessary) using the Drupal AJAX
-     * framework, then opens a dialog at the specified Drupal path.
-     *
-     * @param {CKEditor} editor
-     *   The CKEditor instance that is opening the dialog.
-     * @param {string} url
-     *   The URL that contains the contents of the dialog.
-     * @param {object} existingValues
-     *   Existing values that will be sent via POST to the url for the dialog
-     *   contents.
-     * @param {function} saveCallback
-     *   A function to be called upon saving the dialog.
-     * @param {object} dialogSettings
-     *   An object containing settings to be passed to the jQuery UI.
-     */
-    openDialog: function (editor, url, existingValues, saveCallback, dialogSettings) {
-      // Locate a suitable place to display our loading indicator.
+    openDialog: function openDialog(editor, url, existingValues, saveCallback, dialogSettings) {
       var $target = $(editor.container.$);
       if (editor.elementMode === CKEDITOR.ELEMENT_MODE_REPLACE) {
         $target = $target.find('.cke_contents');
       }
 
-      // Remove any previous loading indicator.
       $target.css('position', 'relative').find('.ckeditor-dialog-loading').remove();
 
-      // Add a consistent dialog class.
       var classes = dialogSettings.dialogClass ? dialogSettings.dialogClass.split(' ') : [];
       classes.push('ui-dialog--narrow');
       dialogSettings.dialogClass = classes.join(' ');
       dialogSettings.autoResize = window.matchMedia('(min-width: 600px)').matches;
       dialogSettings.width = 'auto';
 
-      // Add a "Loading…" message, hide it underneath the CKEditor toolbar,
-      // create a Drupal.Ajax instance to load the dialog and trigger it.
       var $content = $('<div class="ckeditor-dialog-loading"><span style="top: -40px;" class="ckeditor-dialog-loading-link">' + Drupal.t('Loading...') + '</span></div>');
       $content.appendTo($target);
 
@@ -250,55 +146,47 @@
         dialogType: 'modal',
         selector: '.ckeditor-dialog-loading-link',
         url: url,
-        progress: {type: 'throbber'},
+        progress: { type: 'throbber' },
         submit: {
           editor_object: existingValues
         }
       });
       ckeditorAjaxDialog.execute();
 
-      // After a short delay, show "Loading…" message.
       window.setTimeout(function () {
-        $content.find('span').animate({top: '0px'});
+        $content.find('span').animate({ top: '0px' });
       }, 1000);
 
-      // Store the save callback to be executed when this dialog is closed.
       Drupal.ckeditor.saveCallback = saveCallback;
     }
   };
 
-  // Moves the dialog to the top of the CKEDITOR stack.
   $(window).on('dialogcreate', function (e, dialog, $element, settings) {
     $('.ui-dialog--narrow').css('zIndex', CKEDITOR.config.baseFloatZIndex + 1);
   });
 
-  // Respond to new dialogs that are opened by CKEditor, closing the AJAX loader.
   $(window).on('dialog:beforecreate', function (e, dialog, $element, settings) {
-    $('.ckeditor-dialog-loading').animate({top: '-40px'}, function () {
+    $('.ckeditor-dialog-loading').animate({ top: '-40px' }, function () {
       $(this).remove();
     });
   });
 
-  // Respond to dialogs that are saved, sending data back to CKEditor.
   $(window).on('editor:dialogsave', function (e, values) {
     if (Drupal.ckeditor.saveCallback) {
       Drupal.ckeditor.saveCallback(values);
     }
   });
 
-  // Respond to dialogs that are closed, removing the current save handler.
   $(window).on('dialog:afterclose', function (e, dialog, $element) {
     if (Drupal.ckeditor.saveCallback) {
       Drupal.ckeditor.saveCallback = null;
     }
   });
 
-  // Formulate a default formula for the maximum autoGrow height.
   $(document).on('drupalViewportOffsetChange', function () {
     CKEDITOR.config.autoGrow_maxHeight = 0.7 * (window.innerHeight - displace.offsets.top - displace.offsets.bottom);
   });
 
-  // Redirect on hash change when the original hash has an associated CKEditor.
   function redirectTextareaFragmentToCKEditorInstance() {
     var hash = location.hash.substr(1);
     var element = document.getElementById(hash);
@@ -312,30 +200,11 @@
   }
   $(window).on('hashchange.ckeditor', redirectTextareaFragmentToCKEditorInstance);
 
-  // Set autoGrow to make the editor grow the moment it is created.
   CKEDITOR.config.autoGrow_onStartup = true;
 
-  // Set the CKEditor cache-busting string to the same value as Drupal.
   CKEDITOR.timestamp = drupalSettings.ckeditor.timestamp;
 
   if (AjaxCommands) {
-
-    /**
-     * Command to add style sheets to a CKEditor instance.
-     *
-     * Works for both iframe and inline CKEditor instances.
-     *
-     * @param {Drupal.Ajax} [ajax]
-     *   {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
-     * @param {object} response
-     *   The response from the Ajax request.
-     * @param {string} response.editor_id
-     *   The CKEditor instance ID.
-     * @param {number} [status]
-     *   The XMLHttpRequest status.
-     *
-     * @see http://docs.ckeditor.com/#!/api/CKEDITOR.dom.document
-     */
     AjaxCommands.prototype.ckeditor_add_stylesheet = function (ajax, response, status) {
       var editor = CKEDITOR.instances[response.editor_id];
 
@@ -346,5 +215,4 @@
       }
     };
   }
-
-})(Drupal, Drupal.debounce, CKEDITOR, jQuery, Drupal.displace, Drupal.AjaxCommands);
+})(Drupal, Drupal.debounce, CKEDITOR, jQuery, Drupal.displace, Drupal.AjaxCommands);
\ No newline at end of file
diff --git a/core/modules/ckeditor/js/ckeditor.language.admin.es6.js b/core/modules/ckeditor/js/ckeditor.language.admin.es6.js
new file mode 100644
index 000000000000..1b9b5a66f07e
--- /dev/null
+++ b/core/modules/ckeditor/js/ckeditor.language.admin.es6.js
@@ -0,0 +1,16 @@
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * Provides the summary for the "language" plugin settings vertical tab.
+   */
+  Drupal.behaviors.ckeditorLanguageSettingsSummary = {
+    attach: function () {
+      $('#edit-editor-settings-plugins-language').drupalSetSummary(function (context) {
+        return $('#edit-editor-settings-plugins-language-language-list-type option:selected').text();
+      });
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/core/modules/ckeditor/js/ckeditor.language.admin.js b/core/modules/ckeditor/js/ckeditor.language.admin.js
index 1b9b5a66f07e..7d7b70107aae 100644
--- a/core/modules/ckeditor/js/ckeditor.language.admin.js
+++ b/core/modules/ckeditor/js/ckeditor.language.admin.js
@@ -1,16 +1,20 @@
+/**
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/ckeditor/js/ckeditor.language.admin.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
+
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   * Provides the summary for the "language" plugin settings vertical tab.
-   */
   Drupal.behaviors.ckeditorLanguageSettingsSummary = {
-    attach: function () {
+    attach: function attach() {
       $('#edit-editor-settings-plugins-language').drupalSetSummary(function (context) {
         return $('#edit-editor-settings-plugins-language-language-list-type option:selected').text();
       });
     }
   };
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/modules/ckeditor/js/ckeditor.stylescombo.admin.es6.js b/core/modules/ckeditor/js/ckeditor.stylescombo.admin.es6.js
new file mode 100644
index 000000000000..c425dbb30028
--- /dev/null
+++ b/core/modules/ckeditor/js/ckeditor.stylescombo.admin.es6.js
@@ -0,0 +1,128 @@
+/**
+ * @file
+ * CKEditor StylesCombo admin behavior.
+ */
+
+(function ($, Drupal, drupalSettings, _) {
+
+  'use strict';
+
+  /**
+   * Ensures that the "stylescombo" button's metadata remains up-to-date.
+   *
+   * Triggers the CKEditorPluginSettingsChanged event whenever the "stylescombo"
+   * plugin settings change, to ensure that the corresponding feature metadata
+   * is immediately updated — i.e. ensure that HTML tags and classes entered
+   * here are known to be "required", which may affect filter settings.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches admin behaviour to the "stylescombo" button.
+   */
+  Drupal.behaviors.ckeditorStylesComboSettings = {
+    attach: function (context) {
+      var $context = $(context);
+
+      // React to changes in the list of user-defined styles: calculate the new
+      // stylesSet setting up to 2 times per second, and if it is different,
+      // fire the CKEditorPluginSettingsChanged event with the updated parts of
+      // the CKEditor configuration. (This will, in turn, cause the hidden
+      // CKEditor instance to be updated and a drupalEditorFeatureModified event
+      // to fire.)
+      var $ckeditorActiveToolbar = $context
+        .find('.ckeditor-toolbar-configuration')
+        .find('.ckeditor-toolbar-active');
+      var previousStylesSet = drupalSettings.ckeditor.hiddenCKEditorConfig.stylesSet;
+      var that = this;
+      $context.find('[name="editor[settings][plugins][stylescombo][styles]"]')
+        .on('blur.ckeditorStylesComboSettings', function () {
+          var styles = $.trim($(this).val());
+          var stylesSet = that._generateStylesSetSetting(styles);
+          if (!_.isEqual(previousStylesSet, stylesSet)) {
+            previousStylesSet = stylesSet;
+            $ckeditorActiveToolbar.trigger('CKEditorPluginSettingsChanged', [
+              {stylesSet: stylesSet}
+            ]);
+          }
+        });
+    },
+
+    /**
+     * Builds the "stylesSet" configuration part of the CKEditor JS settings.
+     *
+     * @see \Drupal\ckeditor\Plugin\ckeditor\plugin\StylesCombo::generateStylesSetSetting()
+     *
+     * Note that this is a more forgiving implementation than the PHP version:
+     * the parsing works identically, but instead of failing on invalid styles,
+     * we just ignore those.
+     *
+     * @param {string} styles
+     *   The "styles" setting.
+     *
+     * @return {Array}
+     *   An array containing the "stylesSet" configuration.
+     */
+    _generateStylesSetSetting: function (styles) {
+      var stylesSet = [];
+
+      styles = styles.replace(/\r/g, '\n');
+      var lines = styles.split('\n');
+      for (var i = 0; i < lines.length; i++) {
+        var style = $.trim(lines[i]);
+
+        // Ignore empty lines in between non-empty lines.
+        if (style.length === 0) {
+          continue;
+        }
+
+        // Validate syntax: element[.class...]|label pattern expected.
+        if (style.match(/^ *[a-zA-Z0-9]+ *(\.[a-zA-Z0-9_-]+ *)*\| *.+ *$/) === null) {
+          // Instead of failing, we just ignore any invalid styles.
+          continue;
+        }
+
+        // Parse.
+        var parts = style.split('|');
+        var selector = parts[0];
+        var label = parts[1];
+        var classes = selector.split('.');
+        var element = classes.shift();
+
+        // Build the data structure CKEditor's stylescombo plugin expects.
+        // @see http://docs.cksource.com/CKEditor_3.x/Developers_Guide/Styles
+        stylesSet.push({
+          attributes: {class: classes.join(' ')},
+          element: element,
+          name: label
+        });
+      }
+
+      return stylesSet;
+    }
+  };
+
+  /**
+   * Provides the summary for the "stylescombo" plugin settings vertical tab.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches summary behaviour to the plugin settings vertical tab.
+   */
+  Drupal.behaviors.ckeditorStylesComboSettingsSummary = {
+    attach: function () {
+      $('[data-ckeditor-plugin-id="stylescombo"]').drupalSetSummary(function (context) {
+        var styles = $.trim($('[data-drupal-selector="edit-editor-settings-plugins-stylescombo-styles"]').val());
+        if (styles.length === 0) {
+          return Drupal.t('No styles configured');
+        }
+        else {
+          var count = $.trim(styles).split('\n').length;
+          return Drupal.t('@count styles configured', {'@count': count});
+        }
+      });
+    }
+  };
+
+})(jQuery, Drupal, drupalSettings, _);
diff --git a/core/modules/ckeditor/js/ckeditor.stylescombo.admin.js b/core/modules/ckeditor/js/ckeditor.stylescombo.admin.js
index c425dbb30028..f90dfb6cf623 100644
--- a/core/modules/ckeditor/js/ckeditor.stylescombo.admin.js
+++ b/core/modules/ckeditor/js/ckeditor.stylescombo.admin.js
@@ -1,69 +1,33 @@
 /**
- * @file
- * CKEditor StylesCombo admin behavior.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/ckeditor/js/ckeditor.stylescombo.admin.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings, _) {
 
   'use strict';
 
-  /**
-   * Ensures that the "stylescombo" button's metadata remains up-to-date.
-   *
-   * Triggers the CKEditorPluginSettingsChanged event whenever the "stylescombo"
-   * plugin settings change, to ensure that the corresponding feature metadata
-   * is immediately updated — i.e. ensure that HTML tags and classes entered
-   * here are known to be "required", which may affect filter settings.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches admin behaviour to the "stylescombo" button.
-   */
   Drupal.behaviors.ckeditorStylesComboSettings = {
-    attach: function (context) {
+    attach: function attach(context) {
       var $context = $(context);
 
-      // React to changes in the list of user-defined styles: calculate the new
-      // stylesSet setting up to 2 times per second, and if it is different,
-      // fire the CKEditorPluginSettingsChanged event with the updated parts of
-      // the CKEditor configuration. (This will, in turn, cause the hidden
-      // CKEditor instance to be updated and a drupalEditorFeatureModified event
-      // to fire.)
-      var $ckeditorActiveToolbar = $context
-        .find('.ckeditor-toolbar-configuration')
-        .find('.ckeditor-toolbar-active');
+      var $ckeditorActiveToolbar = $context.find('.ckeditor-toolbar-configuration').find('.ckeditor-toolbar-active');
       var previousStylesSet = drupalSettings.ckeditor.hiddenCKEditorConfig.stylesSet;
       var that = this;
-      $context.find('[name="editor[settings][plugins][stylescombo][styles]"]')
-        .on('blur.ckeditorStylesComboSettings', function () {
-          var styles = $.trim($(this).val());
-          var stylesSet = that._generateStylesSetSetting(styles);
-          if (!_.isEqual(previousStylesSet, stylesSet)) {
-            previousStylesSet = stylesSet;
-            $ckeditorActiveToolbar.trigger('CKEditorPluginSettingsChanged', [
-              {stylesSet: stylesSet}
-            ]);
-          }
-        });
+      $context.find('[name="editor[settings][plugins][stylescombo][styles]"]').on('blur.ckeditorStylesComboSettings', function () {
+        var styles = $.trim($(this).val());
+        var stylesSet = that._generateStylesSetSetting(styles);
+        if (!_.isEqual(previousStylesSet, stylesSet)) {
+          previousStylesSet = stylesSet;
+          $ckeditorActiveToolbar.trigger('CKEditorPluginSettingsChanged', [{ stylesSet: stylesSet }]);
+        }
+      });
     },
 
-    /**
-     * Builds the "stylesSet" configuration part of the CKEditor JS settings.
-     *
-     * @see \Drupal\ckeditor\Plugin\ckeditor\plugin\StylesCombo::generateStylesSetSetting()
-     *
-     * Note that this is a more forgiving implementation than the PHP version:
-     * the parsing works identically, but instead of failing on invalid styles,
-     * we just ignore those.
-     *
-     * @param {string} styles
-     *   The "styles" setting.
-     *
-     * @return {Array}
-     *   An array containing the "stylesSet" configuration.
-     */
-    _generateStylesSetSetting: function (styles) {
+    _generateStylesSetSetting: function _generateStylesSetSetting(styles) {
       var stylesSet = [];
 
       styles = styles.replace(/\r/g, '\n');
@@ -71,28 +35,22 @@
       for (var i = 0; i < lines.length; i++) {
         var style = $.trim(lines[i]);
 
-        // Ignore empty lines in between non-empty lines.
         if (style.length === 0) {
           continue;
         }
 
-        // Validate syntax: element[.class...]|label pattern expected.
         if (style.match(/^ *[a-zA-Z0-9]+ *(\.[a-zA-Z0-9_-]+ *)*\| *.+ *$/) === null) {
-          // Instead of failing, we just ignore any invalid styles.
           continue;
         }
 
-        // Parse.
         var parts = style.split('|');
         var selector = parts[0];
         var label = parts[1];
         var classes = selector.split('.');
         var element = classes.shift();
 
-        // Build the data structure CKEditor's stylescombo plugin expects.
-        // @see http://docs.cksource.com/CKEditor_3.x/Developers_Guide/Styles
         stylesSet.push({
-          attributes: {class: classes.join(' ')},
+          attributes: { class: classes.join(' ') },
           element: element,
           name: label
         });
@@ -102,27 +60,17 @@
     }
   };
 
-  /**
-   * Provides the summary for the "stylescombo" plugin settings vertical tab.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches summary behaviour to the plugin settings vertical tab.
-   */
   Drupal.behaviors.ckeditorStylesComboSettingsSummary = {
-    attach: function () {
+    attach: function attach() {
       $('[data-ckeditor-plugin-id="stylescombo"]').drupalSetSummary(function (context) {
         var styles = $.trim($('[data-drupal-selector="edit-editor-settings-plugins-stylescombo-styles"]').val());
         if (styles.length === 0) {
           return Drupal.t('No styles configured');
-        }
-        else {
+        } else {
           var count = $.trim(styles).split('\n').length;
-          return Drupal.t('@count styles configured', {'@count': count});
+          return Drupal.t('@count styles configured', { '@count': count });
         }
       });
     }
   };
-
-})(jQuery, Drupal, drupalSettings, _);
+})(jQuery, Drupal, drupalSettings, _);
\ No newline at end of file
diff --git a/core/modules/ckeditor/js/models/Model.es6.js b/core/modules/ckeditor/js/models/Model.es6.js
new file mode 100644
index 000000000000..162e363eadf5
--- /dev/null
+++ b/core/modules/ckeditor/js/models/Model.es6.js
@@ -0,0 +1,75 @@
+/**
+ * @file
+ * A Backbone Model for the state of a CKEditor toolbar configuration .
+ */
+
+(function (Drupal, Backbone) {
+
+  'use strict';
+
+  /**
+   * Backbone model for the CKEditor toolbar configuration state.
+   *
+   * @constructor
+   *
+   * @augments Backbone.Model
+   */
+  Drupal.ckeditor.Model = Backbone.Model.extend(/** @lends Drupal.ckeditor.Model# */{
+
+    /**
+     * Default values.
+     *
+     * @type {object}
+     */
+    defaults: /** @lends Drupal.ckeditor.Model# */{
+
+      /**
+       * The CKEditor configuration that is being manipulated through the UI.
+       */
+      activeEditorConfig: null,
+
+      /**
+       * The textarea that contains the serialized representation of the active
+       * CKEditor configuration.
+       */
+      $textarea: null,
+
+      /**
+       * Tracks whether the active toolbar DOM structure has been changed. When
+       * true, activeEditorConfig needs to be updated, and when that is updated,
+       * $textarea will also be updated.
+       */
+      isDirty: false,
+
+      /**
+       * The configuration for the hidden CKEditor instance that is used to
+       * build the features metadata.
+       */
+      hiddenEditorConfig: null,
+
+      /**
+       * A hash that maps buttons to features.
+       */
+      buttonsToFeatures: null,
+
+      /**
+       * A hash, keyed by a feature name, that details CKEditor plugin features.
+       */
+      featuresMetadata: null,
+
+      /**
+       * Whether the button group names are currently visible.
+       */
+      groupNamesVisible: false
+    },
+
+    /**
+     * @method
+     */
+    sync: function () {
+      // Push the settings into the textarea.
+      this.get('$textarea').val(JSON.stringify(this.get('activeEditorConfig')));
+    }
+  });
+
+})(Drupal, Backbone);
diff --git a/core/modules/ckeditor/js/models/Model.js b/core/modules/ckeditor/js/models/Model.js
index 162e363eadf5..c5cff3245bdc 100644
--- a/core/modules/ckeditor/js/models/Model.js
+++ b/core/modules/ckeditor/js/models/Model.js
@@ -1,75 +1,34 @@
 /**
- * @file
- * A Backbone Model for the state of a CKEditor toolbar configuration .
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/ckeditor/js/models/Model.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function (Drupal, Backbone) {
 
   'use strict';
 
-  /**
-   * Backbone model for the CKEditor toolbar configuration state.
-   *
-   * @constructor
-   *
-   * @augments Backbone.Model
-   */
-  Drupal.ckeditor.Model = Backbone.Model.extend(/** @lends Drupal.ckeditor.Model# */{
-
-    /**
-     * Default values.
-     *
-     * @type {object}
-     */
-    defaults: /** @lends Drupal.ckeditor.Model# */{
-
-      /**
-       * The CKEditor configuration that is being manipulated through the UI.
-       */
+  Drupal.ckeditor.Model = Backbone.Model.extend({
+    defaults: {
       activeEditorConfig: null,
 
-      /**
-       * The textarea that contains the serialized representation of the active
-       * CKEditor configuration.
-       */
       $textarea: null,
 
-      /**
-       * Tracks whether the active toolbar DOM structure has been changed. When
-       * true, activeEditorConfig needs to be updated, and when that is updated,
-       * $textarea will also be updated.
-       */
       isDirty: false,
 
-      /**
-       * The configuration for the hidden CKEditor instance that is used to
-       * build the features metadata.
-       */
       hiddenEditorConfig: null,
 
-      /**
-       * A hash that maps buttons to features.
-       */
       buttonsToFeatures: null,
 
-      /**
-       * A hash, keyed by a feature name, that details CKEditor plugin features.
-       */
       featuresMetadata: null,
 
-      /**
-       * Whether the button group names are currently visible.
-       */
       groupNamesVisible: false
     },
 
-    /**
-     * @method
-     */
-    sync: function () {
-      // Push the settings into the textarea.
+    sync: function sync() {
       this.get('$textarea').val(JSON.stringify(this.get('activeEditorConfig')));
     }
   });
-
-})(Drupal, Backbone);
+})(Drupal, Backbone);
\ No newline at end of file
diff --git a/core/modules/ckeditor/js/plugins/drupalimage/plugin.es6.js b/core/modules/ckeditor/js/plugins/drupalimage/plugin.es6.js
new file mode 100644
index 000000000000..0025664e09f7
--- /dev/null
+++ b/core/modules/ckeditor/js/plugins/drupalimage/plugin.es6.js
@@ -0,0 +1,371 @@
+/**
+ * @file
+ * Drupal Image plugin.
+ *
+ * This alters the existing CKEditor image2 widget plugin to:
+ * - require a data-entity-type and a data-entity-uuid attribute (which Drupal
+ *   uses to track where images are being used)
+ * - use a Drupal-native dialog (that is in fact just an alterable Drupal form
+ *   like any other) instead of CKEditor's own dialogs.
+ *
+ * @see \Drupal\editor\Form\EditorImageDialog
+ *
+ * @ignore
+ */
+
+(function ($, Drupal, CKEDITOR) {
+
+  'use strict';
+
+  CKEDITOR.plugins.add('drupalimage', {
+    requires: 'image2',
+    icons: 'drupalimage',
+    hidpi: true,
+
+    beforeInit: function (editor) {
+      // Override the image2 widget definition to require and handle the
+      // additional data-entity-type and data-entity-uuid attributes.
+      editor.on('widgetDefinition', function (event) {
+        var widgetDefinition = event.data;
+        if (widgetDefinition.name !== 'image') {
+          return;
+        }
+
+        // First, convert requiredContent & allowedContent from the string
+        // format that image2 uses for both to formats that are better suited
+        // for extending, so that both this basic drupalimage plugin and Drupal
+        // modules can easily extend it.
+        // @see http://docs.ckeditor.com/#!/api/CKEDITOR.filter.allowedContentRules
+        // Mapped from image2's allowedContent. Unlike image2, we don't allow
+        // <figure>, <figcaption>, <div> or <p>  in our downcast, so we omit
+        // those. For the <img> tag, we list all attributes it lists, but omit
+        // the classes, because the listed classes are for alignment, and for
+        // alignment we use the data-align attribute.
+        widgetDefinition.allowedContent = {
+          img: {
+            attributes: {
+              '!src': true,
+              '!alt': true,
+              'width': true,
+              'height': true
+            },
+            classes: {}
+          }
+        };
+        // Mapped from image2's requiredContent: "img[src,alt]". This does not
+        // use the object format unlike above, but a CKEDITOR.style instance,
+        // because requiredContent does not support the object format.
+        // @see https://www.drupal.org/node/2585173#comment-10456981
+        widgetDefinition.requiredContent = new CKEDITOR.style({
+          element: 'img',
+          attributes: {
+            src: '',
+            alt: ''
+          }
+        });
+
+        // Extend requiredContent & allowedContent.
+        // CKEDITOR.style is an immutable object: we cannot modify its
+        // definition to extend requiredContent. Hence we get the definition,
+        // modify it, and pass it to a new CKEDITOR.style instance.
+        var requiredContent = widgetDefinition.requiredContent.getDefinition();
+        requiredContent.attributes['data-entity-type'] = '';
+        requiredContent.attributes['data-entity-uuid'] = '';
+        widgetDefinition.requiredContent = new CKEDITOR.style(requiredContent);
+        widgetDefinition.allowedContent.img.attributes['!data-entity-type'] = true;
+        widgetDefinition.allowedContent.img.attributes['!data-entity-uuid'] = true;
+
+        // Override downcast(): since we only accept <img> in our upcast method,
+        // the element is already correct. We only need to update the element's
+        // data-entity-uuid attribute.
+        widgetDefinition.downcast = function (element) {
+          element.attributes['data-entity-type'] = this.data['data-entity-type'];
+          element.attributes['data-entity-uuid'] = this.data['data-entity-uuid'];
+        };
+
+        // We want to upcast <img> elements to a DOM structure required by the
+        // image2 widget; we only accept an <img> tag, and that <img> tag MAY
+        // have a data-entity-type and a data-entity-uuid attribute.
+        widgetDefinition.upcast = function (element, data) {
+          if (element.name !== 'img') {
+            return;
+          }
+          // Don't initialize on pasted fake objects.
+          else if (element.attributes['data-cke-realelement']) {
+            return;
+          }
+
+          // Parse the data-entity-type attribute.
+          data['data-entity-type'] = element.attributes['data-entity-type'];
+          // Parse the data-entity-uuid attribute.
+          data['data-entity-uuid'] = element.attributes['data-entity-uuid'];
+
+          return element;
+        };
+
+        // Overrides default implementation. Used to populate the "classes"
+        // property of the widget's "data" property, which is used for the
+        // "widget styles" functionality
+        // (http://docs.ckeditor.com/#!/guide/dev_styles-section-widget-styles).
+        // Is applied to whatever the main element of the widget is (<figure> or
+        // <img>). The classes in image2_captionedClass are always added due to
+        // a bug in CKEditor. In the case of drupalimage, we don't ever want to
+        // add that class, because the widget template already contains it.
+        // @see http://dev.ckeditor.com/ticket/13888
+        // @see https://www.drupal.org/node/2268941
+        var originalGetClasses = widgetDefinition.getClasses;
+        widgetDefinition.getClasses = function () {
+          var classes = originalGetClasses.call(this);
+          var captionedClasses = (this.editor.config.image2_captionedClass || '').split(/\s+/);
+
+          if (captionedClasses.length && classes) {
+            for (var i = 0; i < captionedClasses.length; i++) {
+              if (captionedClasses[i] in classes) {
+                delete classes[captionedClasses[i]];
+              }
+            }
+          }
+
+          return classes;
+        };
+
+        // Protected; keys of the widget data to be sent to the Drupal dialog.
+        // Keys in the hash are the keys for image2's data, values are the keys
+        // that the Drupal dialog uses.
+        widgetDefinition._mapDataToDialog = {
+          'src': 'src',
+          'alt': 'alt',
+          'width': 'width',
+          'height': 'height',
+          'data-entity-type': 'data-entity-type',
+          'data-entity-uuid': 'data-entity-uuid'
+        };
+
+        // Protected; transforms widget's data object to the format used by the
+        // \Drupal\editor\Form\EditorImageDialog dialog, keeping only the data
+        // listed in widgetDefinition._dataForDialog.
+        widgetDefinition._dataToDialogValues = function (data) {
+          var dialogValues = {};
+          var map = widgetDefinition._mapDataToDialog;
+          Object.keys(widgetDefinition._mapDataToDialog).forEach(function (key) {
+            dialogValues[map[key]] = data[key];
+          });
+          return dialogValues;
+        };
+
+        // Protected; the inverse of _dataToDialogValues.
+        widgetDefinition._dialogValuesToData = function (dialogReturnValues) {
+          var data = {};
+          var map = widgetDefinition._mapDataToDialog;
+          Object.keys(widgetDefinition._mapDataToDialog).forEach(function (key) {
+            if (dialogReturnValues.hasOwnProperty(map[key])) {
+              data[key] = dialogReturnValues[map[key]];
+            }
+          });
+          return data;
+        };
+
+        // Protected; creates Drupal dialog save callback.
+        widgetDefinition._createDialogSaveCallback = function (editor, widget) {
+          return function (dialogReturnValues) {
+            var firstEdit = !widget.ready;
+
+            // Dialog may have blurred the widget. Re-focus it first.
+            if (!firstEdit) {
+              widget.focus();
+            }
+
+            editor.fire('saveSnapshot');
+
+            // Pass `true` so DocumentFragment will also be returned.
+            var container = widget.wrapper.getParent(true);
+            var image = widget.parts.image;
+
+            // Set the updated widget data, after the necessary conversions from
+            // the dialog's return values.
+            // Note: on widget#setData this widget instance might be destroyed.
+            var data = widgetDefinition._dialogValuesToData(dialogReturnValues.attributes);
+            widget.setData(data);
+
+            // Retrieve the widget once again. It could've been destroyed
+            // when shifting state, so might deal with a new instance.
+            widget = editor.widgets.getByElement(image);
+
+            // It's first edit, just after widget instance creation, but before
+            // it was inserted into DOM. So we need to retrieve the widget
+            // wrapper from inside the DocumentFragment which we cached above
+            // and finalize other things (like ready event and flag).
+            if (firstEdit) {
+              editor.widgets.finalizeCreation(container);
+            }
+
+            setTimeout(function () {
+              // (Re-)focus the widget.
+              widget.focus();
+              // Save snapshot for undo support.
+              editor.fire('saveSnapshot');
+            });
+
+            return widget;
+          };
+        };
+
+        var originalInit = widgetDefinition.init;
+        widgetDefinition.init = function () {
+          originalInit.call(this);
+
+          // Update data.link object with attributes if the link has been
+          // discovered.
+          // @see plugins/image2/plugin.js/init() in CKEditor; this is similar.
+          if (this.parts.link) {
+            this.setData('link', CKEDITOR.plugins.image2.getLinkAttributesParser()(editor, this.parts.link));
+          }
+        };
+      });
+
+      // Add a widget#edit listener to every instance of image2 widget in order
+      // to handle its editing with a Drupal-native dialog.
+      // This includes also a case just after the image was created
+      // and dialog should be opened for it for the first time.
+      editor.widgets.on('instanceCreated', function (event) {
+        var widget = event.data;
+
+        if (widget.name !== 'image') {
+          return;
+        }
+
+        widget.on('edit', function (event) {
+          // Cancel edit event to break image2's dialog binding
+          // (and also to prevent automatic insertion before opening dialog).
+          event.cancel();
+
+          // Open drupalimage dialog.
+          editor.execCommand('editdrupalimage', {
+            existingValues: widget.definition._dataToDialogValues(widget.data),
+            saveCallback: widget.definition._createDialogSaveCallback(editor, widget),
+            // Drupal.t() will not work inside CKEditor plugins because CKEditor
+            // loads the JavaScript file instead of Drupal. Pull translated
+            // strings from the plugin settings that are translated server-side.
+            dialogTitle: widget.data.src ? editor.config.drupalImage_dialogTitleEdit : editor.config.drupalImage_dialogTitleAdd
+          });
+        });
+      });
+
+      // Register the "editdrupalimage" command, which essentially just replaces
+      // the "image" command's CKEditor dialog with a Drupal-native dialog.
+      editor.addCommand('editdrupalimage', {
+        allowedContent: 'img[alt,!src,width,height,!data-entity-type,!data-entity-uuid]',
+        requiredContent: 'img[alt,src,data-entity-type,data-entity-uuid]',
+        modes: {wysiwyg: 1},
+        canUndo: true,
+        exec: function (editor, data) {
+          var dialogSettings = {
+            title: data.dialogTitle,
+            dialogClass: 'editor-image-dialog'
+          };
+          Drupal.ckeditor.openDialog(editor, Drupal.url('editor/dialog/image/' + editor.config.drupal.format), data.existingValues, data.saveCallback, dialogSettings);
+        }
+      });
+
+      // Register the toolbar button.
+      if (editor.ui.addButton) {
+        editor.ui.addButton('DrupalImage', {
+          label: Drupal.t('Image'),
+          // Note that we use the original image2 command!
+          command: 'image'
+        });
+      }
+    },
+
+    afterInit: function (editor) {
+      linkCommandIntegrator(editor);
+    }
+
+  });
+
+  // Override image2's integration with the official CKEditor link plugin:
+  // integrate with the drupallink plugin instead.
+  CKEDITOR.plugins.image2.getLinkAttributesParser = function () {
+    return CKEDITOR.plugins.drupallink.parseLinkAttributes;
+  };
+  CKEDITOR.plugins.image2.getLinkAttributesGetter = function () {
+    return CKEDITOR.plugins.drupallink.getLinkAttributes;
+  };
+
+  /**
+   * Integrates the drupalimage widget with the drupallink plugin.
+   *
+   * Makes images linkable.
+   *
+   * @param {CKEDITOR.editor} editor
+   *   A CKEditor instance.
+   */
+  function linkCommandIntegrator(editor) {
+    // Nothing to integrate with if the drupallink plugin is not loaded.
+    if (!editor.plugins.drupallink) {
+      return;
+    }
+
+    // Override default behaviour of 'drupalunlink' command.
+    editor.getCommand('drupalunlink').on('exec', function (evt) {
+      var widget = getFocusedWidget(editor);
+
+      // Override 'drupalunlink' only when link truly belongs to the widget. If
+      // wrapped inline widget in a link, let default unlink work.
+      // @see https://dev.ckeditor.com/ticket/11814
+      if (!widget || !widget.parts.link) {
+        return;
+      }
+
+      widget.setData('link', null);
+
+      // Selection (which is fake) may not change if unlinked image in focused
+      // widget, i.e. if captioned image. Let's refresh command state manually
+      // here.
+      this.refresh(editor, editor.elementPath());
+
+      evt.cancel();
+    });
+
+    // Override default refresh of 'drupalunlink' command.
+    editor.getCommand('drupalunlink').on('refresh', function (evt) {
+      var widget = getFocusedWidget(editor);
+
+      if (!widget) {
+        return;
+      }
+
+      // Note that widget may be wrapped in a link, which
+      // does not belong to that widget (#11814).
+      this.setState(widget.data.link || widget.wrapper.getAscendant('a') ?
+        CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED);
+
+      evt.cancel();
+    });
+  }
+
+  /**
+   * Gets the focused widget, if of the type specific for this plugin.
+   *
+   * @param {CKEDITOR.editor} editor
+   *   A CKEditor instance.
+   *
+   * @return {?CKEDITOR.plugins.widget}
+   *   The focused image2 widget instance, or null.
+   */
+  function getFocusedWidget(editor) {
+    var widget = editor.widgets.focused;
+
+    if (widget && widget.name === 'image') {
+      return widget;
+    }
+
+    return null;
+  }
+
+  // Expose an API for other plugins to interact with drupalimage widgets.
+  CKEDITOR.plugins.drupalimage = {
+    getFocusedWidget: getFocusedWidget
+  };
+
+})(jQuery, Drupal, CKEDITOR);
diff --git a/core/modules/ckeditor/js/plugins/drupalimage/plugin.js b/core/modules/ckeditor/js/plugins/drupalimage/plugin.js
index 0025664e09f7..da9171958561 100644
--- a/core/modules/ckeditor/js/plugins/drupalimage/plugin.js
+++ b/core/modules/ckeditor/js/plugins/drupalimage/plugin.js
@@ -1,17 +1,10 @@
 /**
- * @file
- * Drupal Image plugin.
- *
- * This alters the existing CKEditor image2 widget plugin to:
- * - require a data-entity-type and a data-entity-uuid attribute (which Drupal
- *   uses to track where images are being used)
- * - use a Drupal-native dialog (that is in fact just an alterable Drupal form
- *   like any other) instead of CKEditor's own dialogs.
- *
- * @see \Drupal\editor\Form\EditorImageDialog
- *
- * @ignore
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/ckeditor/js/plugins/drupalimage/plugin.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, CKEDITOR) {
 
@@ -22,25 +15,13 @@
     icons: 'drupalimage',
     hidpi: true,
 
-    beforeInit: function (editor) {
-      // Override the image2 widget definition to require and handle the
-      // additional data-entity-type and data-entity-uuid attributes.
+    beforeInit: function beforeInit(editor) {
       editor.on('widgetDefinition', function (event) {
         var widgetDefinition = event.data;
         if (widgetDefinition.name !== 'image') {
           return;
         }
 
-        // First, convert requiredContent & allowedContent from the string
-        // format that image2 uses for both to formats that are better suited
-        // for extending, so that both this basic drupalimage plugin and Drupal
-        // modules can easily extend it.
-        // @see http://docs.ckeditor.com/#!/api/CKEDITOR.filter.allowedContentRules
-        // Mapped from image2's allowedContent. Unlike image2, we don't allow
-        // <figure>, <figcaption>, <div> or <p>  in our downcast, so we omit
-        // those. For the <img> tag, we list all attributes it lists, but omit
-        // the classes, because the listed classes are for alignment, and for
-        // alignment we use the data-align attribute.
         widgetDefinition.allowedContent = {
           img: {
             attributes: {
@@ -52,10 +33,7 @@
             classes: {}
           }
         };
-        // Mapped from image2's requiredContent: "img[src,alt]". This does not
-        // use the object format unlike above, but a CKEDITOR.style instance,
-        // because requiredContent does not support the object format.
-        // @see https://www.drupal.org/node/2585173#comment-10456981
+
         widgetDefinition.requiredContent = new CKEDITOR.style({
           element: 'img',
           attributes: {
@@ -64,10 +42,6 @@
           }
         });
 
-        // Extend requiredContent & allowedContent.
-        // CKEDITOR.style is an immutable object: we cannot modify its
-        // definition to extend requiredContent. Hence we get the definition,
-        // modify it, and pass it to a new CKEDITOR.style instance.
         var requiredContent = widgetDefinition.requiredContent.getDefinition();
         requiredContent.attributes['data-entity-type'] = '';
         requiredContent.attributes['data-entity-uuid'] = '';
@@ -75,44 +49,25 @@
         widgetDefinition.allowedContent.img.attributes['!data-entity-type'] = true;
         widgetDefinition.allowedContent.img.attributes['!data-entity-uuid'] = true;
 
-        // Override downcast(): since we only accept <img> in our upcast method,
-        // the element is already correct. We only need to update the element's
-        // data-entity-uuid attribute.
         widgetDefinition.downcast = function (element) {
           element.attributes['data-entity-type'] = this.data['data-entity-type'];
           element.attributes['data-entity-uuid'] = this.data['data-entity-uuid'];
         };
 
-        // We want to upcast <img> elements to a DOM structure required by the
-        // image2 widget; we only accept an <img> tag, and that <img> tag MAY
-        // have a data-entity-type and a data-entity-uuid attribute.
         widgetDefinition.upcast = function (element, data) {
           if (element.name !== 'img') {
             return;
-          }
-          // Don't initialize on pasted fake objects.
-          else if (element.attributes['data-cke-realelement']) {
-            return;
-          }
+          } else if (element.attributes['data-cke-realelement']) {
+              return;
+            }
 
-          // Parse the data-entity-type attribute.
           data['data-entity-type'] = element.attributes['data-entity-type'];
-          // Parse the data-entity-uuid attribute.
+
           data['data-entity-uuid'] = element.attributes['data-entity-uuid'];
 
           return element;
         };
 
-        // Overrides default implementation. Used to populate the "classes"
-        // property of the widget's "data" property, which is used for the
-        // "widget styles" functionality
-        // (http://docs.ckeditor.com/#!/guide/dev_styles-section-widget-styles).
-        // Is applied to whatever the main element of the widget is (<figure> or
-        // <img>). The classes in image2_captionedClass are always added due to
-        // a bug in CKEditor. In the case of drupalimage, we don't ever want to
-        // add that class, because the widget template already contains it.
-        // @see http://dev.ckeditor.com/ticket/13888
-        // @see https://www.drupal.org/node/2268941
         var originalGetClasses = widgetDefinition.getClasses;
         widgetDefinition.getClasses = function () {
           var classes = originalGetClasses.call(this);
@@ -129,9 +84,6 @@
           return classes;
         };
 
-        // Protected; keys of the widget data to be sent to the Drupal dialog.
-        // Keys in the hash are the keys for image2's data, values are the keys
-        // that the Drupal dialog uses.
         widgetDefinition._mapDataToDialog = {
           'src': 'src',
           'alt': 'alt',
@@ -141,9 +93,6 @@
           'data-entity-uuid': 'data-entity-uuid'
         };
 
-        // Protected; transforms widget's data object to the format used by the
-        // \Drupal\editor\Form\EditorImageDialog dialog, keeping only the data
-        // listed in widgetDefinition._dataForDialog.
         widgetDefinition._dataToDialogValues = function (data) {
           var dialogValues = {};
           var map = widgetDefinition._mapDataToDialog;
@@ -153,7 +102,6 @@
           return dialogValues;
         };
 
-        // Protected; the inverse of _dataToDialogValues.
         widgetDefinition._dialogValuesToData = function (dialogReturnValues) {
           var data = {};
           var map = widgetDefinition._mapDataToDialog;
@@ -165,44 +113,31 @@
           return data;
         };
 
-        // Protected; creates Drupal dialog save callback.
         widgetDefinition._createDialogSaveCallback = function (editor, widget) {
           return function (dialogReturnValues) {
             var firstEdit = !widget.ready;
 
-            // Dialog may have blurred the widget. Re-focus it first.
             if (!firstEdit) {
               widget.focus();
             }
 
             editor.fire('saveSnapshot');
 
-            // Pass `true` so DocumentFragment will also be returned.
             var container = widget.wrapper.getParent(true);
             var image = widget.parts.image;
 
-            // Set the updated widget data, after the necessary conversions from
-            // the dialog's return values.
-            // Note: on widget#setData this widget instance might be destroyed.
             var data = widgetDefinition._dialogValuesToData(dialogReturnValues.attributes);
             widget.setData(data);
 
-            // Retrieve the widget once again. It could've been destroyed
-            // when shifting state, so might deal with a new instance.
             widget = editor.widgets.getByElement(image);
 
-            // It's first edit, just after widget instance creation, but before
-            // it was inserted into DOM. So we need to retrieve the widget
-            // wrapper from inside the DocumentFragment which we cached above
-            // and finalize other things (like ready event and flag).
             if (firstEdit) {
               editor.widgets.finalizeCreation(container);
             }
 
             setTimeout(function () {
-              // (Re-)focus the widget.
               widget.focus();
-              // Save snapshot for undo support.
+
               editor.fire('saveSnapshot');
             });
 
@@ -214,19 +149,12 @@
         widgetDefinition.init = function () {
           originalInit.call(this);
 
-          // Update data.link object with attributes if the link has been
-          // discovered.
-          // @see plugins/image2/plugin.js/init() in CKEditor; this is similar.
           if (this.parts.link) {
             this.setData('link', CKEDITOR.plugins.image2.getLinkAttributesParser()(editor, this.parts.link));
           }
         };
       });
 
-      // Add a widget#edit listener to every instance of image2 widget in order
-      // to handle its editing with a Drupal-native dialog.
-      // This includes also a case just after the image was created
-      // and dialog should be opened for it for the first time.
       editor.widgets.on('instanceCreated', function (event) {
         var widget = event.data;
 
@@ -235,30 +163,23 @@
         }
 
         widget.on('edit', function (event) {
-          // Cancel edit event to break image2's dialog binding
-          // (and also to prevent automatic insertion before opening dialog).
           event.cancel();
 
-          // Open drupalimage dialog.
           editor.execCommand('editdrupalimage', {
             existingValues: widget.definition._dataToDialogValues(widget.data),
             saveCallback: widget.definition._createDialogSaveCallback(editor, widget),
-            // Drupal.t() will not work inside CKEditor plugins because CKEditor
-            // loads the JavaScript file instead of Drupal. Pull translated
-            // strings from the plugin settings that are translated server-side.
+
             dialogTitle: widget.data.src ? editor.config.drupalImage_dialogTitleEdit : editor.config.drupalImage_dialogTitleAdd
           });
         });
       });
 
-      // Register the "editdrupalimage" command, which essentially just replaces
-      // the "image" command's CKEditor dialog with a Drupal-native dialog.
       editor.addCommand('editdrupalimage', {
         allowedContent: 'img[alt,!src,width,height,!data-entity-type,!data-entity-uuid]',
         requiredContent: 'img[alt,src,data-entity-type,data-entity-uuid]',
-        modes: {wysiwyg: 1},
+        modes: { wysiwyg: 1 },
         canUndo: true,
-        exec: function (editor, data) {
+        exec: function exec(editor, data) {
           var dialogSettings = {
             title: data.dialogTitle,
             dialogClass: 'editor-image-dialog'
@@ -267,24 +188,21 @@
         }
       });
 
-      // Register the toolbar button.
       if (editor.ui.addButton) {
         editor.ui.addButton('DrupalImage', {
           label: Drupal.t('Image'),
-          // Note that we use the original image2 command!
+
           command: 'image'
         });
       }
     },
 
-    afterInit: function (editor) {
+    afterInit: function afterInit(editor) {
       linkCommandIntegrator(editor);
     }
 
   });
 
-  // Override image2's integration with the official CKEditor link plugin:
-  // integrate with the drupallink plugin instead.
   CKEDITOR.plugins.image2.getLinkAttributesParser = function () {
     return CKEDITOR.plugins.drupallink.parseLinkAttributes;
   };
@@ -292,42 +210,25 @@
     return CKEDITOR.plugins.drupallink.getLinkAttributes;
   };
 
-  /**
-   * Integrates the drupalimage widget with the drupallink plugin.
-   *
-   * Makes images linkable.
-   *
-   * @param {CKEDITOR.editor} editor
-   *   A CKEditor instance.
-   */
   function linkCommandIntegrator(editor) {
-    // Nothing to integrate with if the drupallink plugin is not loaded.
     if (!editor.plugins.drupallink) {
       return;
     }
 
-    // Override default behaviour of 'drupalunlink' command.
     editor.getCommand('drupalunlink').on('exec', function (evt) {
       var widget = getFocusedWidget(editor);
 
-      // Override 'drupalunlink' only when link truly belongs to the widget. If
-      // wrapped inline widget in a link, let default unlink work.
-      // @see https://dev.ckeditor.com/ticket/11814
       if (!widget || !widget.parts.link) {
         return;
       }
 
       widget.setData('link', null);
 
-      // Selection (which is fake) may not change if unlinked image in focused
-      // widget, i.e. if captioned image. Let's refresh command state manually
-      // here.
       this.refresh(editor, editor.elementPath());
 
       evt.cancel();
     });
 
-    // Override default refresh of 'drupalunlink' command.
     editor.getCommand('drupalunlink').on('refresh', function (evt) {
       var widget = getFocusedWidget(editor);
 
@@ -335,24 +236,12 @@
         return;
       }
 
-      // Note that widget may be wrapped in a link, which
-      // does not belong to that widget (#11814).
-      this.setState(widget.data.link || widget.wrapper.getAscendant('a') ?
-        CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED);
+      this.setState(widget.data.link || widget.wrapper.getAscendant('a') ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED);
 
       evt.cancel();
     });
   }
 
-  /**
-   * Gets the focused widget, if of the type specific for this plugin.
-   *
-   * @param {CKEDITOR.editor} editor
-   *   A CKEditor instance.
-   *
-   * @return {?CKEDITOR.plugins.widget}
-   *   The focused image2 widget instance, or null.
-   */
   function getFocusedWidget(editor) {
     var widget = editor.widgets.focused;
 
@@ -363,9 +252,7 @@
     return null;
   }
 
-  // Expose an API for other plugins to interact with drupalimage widgets.
   CKEDITOR.plugins.drupalimage = {
     getFocusedWidget: getFocusedWidget
   };
-
-})(jQuery, Drupal, CKEDITOR);
+})(jQuery, Drupal, CKEDITOR);
\ No newline at end of file
diff --git a/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.es6.js b/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.es6.js
new file mode 100644
index 000000000000..d1d666c5b945
--- /dev/null
+++ b/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.es6.js
@@ -0,0 +1,301 @@
+/**
+ * @file
+ * Drupal Image Caption plugin.
+ *
+ * This alters the existing CKEditor image2 widget plugin, which is already
+ * altered by the Drupal Image plugin, to:
+ * - allow for the data-caption and data-align attributes to be set
+ * - mimic the upcasting behavior of the caption_filter filter.
+ *
+ * @ignore
+ */
+
+(function (CKEDITOR) {
+
+  'use strict';
+
+  CKEDITOR.plugins.add('drupalimagecaption', {
+    requires: 'drupalimage',
+
+    beforeInit: function (editor) {
+      // Disable default placeholder text that comes with CKEditor's image2
+      // plugin: it has an inferior UX (it requires the user to manually delete
+      // the place holder text).
+      editor.lang.image2.captionPlaceholder = '';
+
+      // Drupal.t() will not work inside CKEditor plugins because CKEditor loads
+      // the JavaScript file instead of Drupal. Pull translated strings from the
+      // plugin settings that are translated server-side.
+      var placeholderText = editor.config.drupalImageCaption_captionPlaceholderText;
+
+      // Override the image2 widget definition to handle the additional
+      // data-align and data-caption attributes.
+      editor.on('widgetDefinition', function (event) {
+        var widgetDefinition = event.data;
+        if (widgetDefinition.name !== 'image') {
+          return;
+        }
+
+        // Only perform the downcasting/upcasting for to the enabled filters.
+        var captionFilterEnabled = editor.config.drupalImageCaption_captionFilterEnabled;
+        var alignFilterEnabled = editor.config.drupalImageCaption_alignFilterEnabled;
+
+        // Override default features definitions for drupalimagecaption.
+        CKEDITOR.tools.extend(widgetDefinition.features, {
+          caption: {
+            requiredContent: 'img[data-caption]'
+          },
+          align: {
+            requiredContent: 'img[data-align]'
+          }
+        }, true);
+
+        // Extend requiredContent & allowedContent.
+        // CKEDITOR.style is an immutable object: we cannot modify its
+        // definition to extend requiredContent. Hence we get the definition,
+        // modify it, and pass it to a new CKEDITOR.style instance.
+        var requiredContent = widgetDefinition.requiredContent.getDefinition();
+        requiredContent.attributes['data-align'] = '';
+        requiredContent.attributes['data-caption'] = '';
+        widgetDefinition.requiredContent = new CKEDITOR.style(requiredContent);
+        widgetDefinition.allowedContent.img.attributes['!data-align'] = true;
+        widgetDefinition.allowedContent.img.attributes['!data-caption'] = true;
+
+        // Override allowedContent setting for the 'caption' nested editable.
+        // This must match what caption_filter enforces.
+        // @see \Drupal\filter\Plugin\Filter\FilterCaption::process()
+        // @see \Drupal\Component\Utility\Xss::filter()
+        widgetDefinition.editables.caption.allowedContent = 'a[!href]; em strong cite code br';
+
+        // Override downcast(): ensure we *only* output <img>, but also ensure
+        // we include the data-entity-type, data-entity-uuid, data-align and
+        // data-caption attributes.
+        var originalDowncast = widgetDefinition.downcast;
+        widgetDefinition.downcast = function (element) {
+          var img = findElementByName(element, 'img');
+          originalDowncast.call(this, img);
+
+          var caption = this.editables.caption;
+          var captionHtml = caption && caption.getData();
+          var attrs = img.attributes;
+
+          if (captionFilterEnabled) {
+            // If image contains a non-empty caption, serialize caption to the
+            // data-caption attribute.
+            if (captionHtml) {
+              attrs['data-caption'] = captionHtml;
+            }
+          }
+          if (alignFilterEnabled) {
+            if (this.data.align !== 'none') {
+              attrs['data-align'] = this.data.align;
+            }
+          }
+
+          // If img is wrapped with a link, we want to return that link.
+          if (img.parent.name === 'a') {
+            return img.parent;
+          }
+          else {
+            return img;
+          }
+        };
+
+        // We want to upcast <img> elements to a DOM structure required by the
+        // image2 widget. Depending on a case it may be:
+        //   - just an <img> tag (non-captioned, not-centered image),
+        //   - <img> tag in a paragraph (non-captioned, centered image),
+        //   - <figure> tag (captioned image).
+        // We take the same attributes into account as downcast() does.
+        var originalUpcast = widgetDefinition.upcast;
+        widgetDefinition.upcast = function (element, data) {
+          if (element.name !== 'img' || !element.attributes['data-entity-type'] || !element.attributes['data-entity-uuid']) {
+            return;
+          }
+          // Don't initialize on pasted fake objects.
+          else if (element.attributes['data-cke-realelement']) {
+            return;
+          }
+
+          element = originalUpcast.call(this, element, data);
+          var attrs = element.attributes;
+
+          if (element.parent.name === 'a') {
+            element = element.parent;
+          }
+
+          var retElement = element;
+          var caption;
+
+          // We won't need the attributes during editing: we'll use widget.data
+          // to store them (except the caption, which is stored in the DOM).
+          if (captionFilterEnabled) {
+            caption = attrs['data-caption'];
+            delete attrs['data-caption'];
+          }
+          if (alignFilterEnabled) {
+            data.align = attrs['data-align'];
+            delete attrs['data-align'];
+          }
+          data['data-entity-type'] = attrs['data-entity-type'];
+          delete attrs['data-entity-type'];
+          data['data-entity-uuid'] = attrs['data-entity-uuid'];
+          delete attrs['data-entity-uuid'];
+
+          if (captionFilterEnabled) {
+            // Unwrap from <p> wrapper created by HTML parser for a captioned
+            // image. The captioned image will be transformed to <figure>, so we
+            // don't want the <p> anymore.
+            if (element.parent.name === 'p' && caption) {
+              var index = element.getIndex();
+              var splitBefore = index > 0;
+              var splitAfter = index + 1 < element.parent.children.length;
+
+              if (splitBefore) {
+                element.parent.split(index);
+              }
+              index = element.getIndex();
+              if (splitAfter) {
+                element.parent.split(index + 1);
+              }
+
+              element.parent.replaceWith(element);
+              retElement = element;
+            }
+
+            // If this image has a caption, create a full <figure> structure.
+            if (caption) {
+              var figure = new CKEDITOR.htmlParser.element('figure');
+              caption = new CKEDITOR.htmlParser.fragment.fromHtml(caption, 'figcaption');
+
+              // Use Drupal's data-placeholder attribute to insert a CSS-based,
+              // translation-ready placeholder for empty captions. Note that it
+              // also must to be done for new instances (see
+              // widgetDefinition._createDialogSaveCallback).
+              caption.attributes['data-placeholder'] = placeholderText;
+
+              element.replaceWith(figure);
+              figure.add(element);
+              figure.add(caption);
+              figure.attributes['class'] = editor.config.image2_captionedClass;
+              retElement = figure;
+            }
+          }
+
+          if (alignFilterEnabled) {
+            // If this image doesn't have a caption (or the caption filter is
+            // disabled), but it is centered, make sure that it's wrapped with
+            // <p>, which will become a part of the widget.
+            if (data.align === 'center' && (!captionFilterEnabled || !caption)) {
+              var p = new CKEDITOR.htmlParser.element('p');
+              element.replaceWith(p);
+              p.add(element);
+              // Apply the class for centered images.
+              p.addClass(editor.config.image2_alignClasses[1]);
+              retElement = p;
+            }
+          }
+
+          // Return the upcasted element (<img>, <figure> or <p>).
+          return retElement;
+        };
+
+        // Protected; keys of the widget data to be sent to the Drupal dialog.
+        // Append to the values defined by the drupalimage plugin.
+        // @see core/modules/ckeditor/js/plugins/drupalimage/plugin.js
+        CKEDITOR.tools.extend(widgetDefinition._mapDataToDialog, {
+          'align': 'data-align',
+          'data-caption': 'data-caption',
+          'hasCaption': 'hasCaption'
+        });
+
+        // Override Drupal dialog save callback.
+        var originalCreateDialogSaveCallback = widgetDefinition._createDialogSaveCallback;
+        widgetDefinition._createDialogSaveCallback = function (editor, widget) {
+          var saveCallback = originalCreateDialogSaveCallback.call(this, editor, widget);
+
+          return function (dialogReturnValues) {
+            // Ensure hasCaption is a boolean. image2 assumes it always works
+            // with booleans; if this is not the case, then
+            // CKEDITOR.plugins.image2.stateShifter() will incorrectly mark
+            // widget.data.hasCaption as "changed" (e.g. when hasCaption === 0
+            // instead of hasCaption === false). This causes image2's "state
+            // shifter" to enter the wrong branch of the algorithm and blow up.
+            dialogReturnValues.attributes.hasCaption = !!dialogReturnValues.attributes.hasCaption;
+
+            var actualWidget = saveCallback(dialogReturnValues);
+
+            // By default, the template of captioned widget has no
+            // data-placeholder attribute. Note that it also must be done when
+            // upcasting existing elements (see widgetDefinition.upcast).
+            if (dialogReturnValues.attributes.hasCaption) {
+              actualWidget.editables.caption.setAttribute('data-placeholder', placeholderText);
+
+              // Some browsers will add a <br> tag to a newly created DOM
+              // element with no content. Remove this <br> if it is the only
+              // thing in the caption. Our placeholder support requires the
+              // element be entirely empty. See filter-caption.css.
+              var captionElement = actualWidget.editables.caption.$;
+              if (captionElement.childNodes.length === 1 && captionElement.childNodes.item(0).nodeName === 'BR') {
+                captionElement.removeChild(captionElement.childNodes.item(0));
+              }
+            }
+          };
+        };
+      // Low priority to ensure drupalimage's event handler runs first.
+      }, null, null, 20);
+    },
+
+    afterInit: function (editor) {
+      var disableButtonIfOnWidget = function (evt) {
+        var widget = editor.widgets.focused;
+        if (widget && widget.name === 'image') {
+          this.setState(CKEDITOR.TRISTATE_DISABLED);
+          evt.cancel();
+        }
+      };
+
+      // Disable alignment buttons if the align filter is not enabled.
+      if (editor.plugins.justify && !editor.config.drupalImageCaption_alignFilterEnabled) {
+        var cmd;
+        var commands = ['justifyleft', 'justifycenter', 'justifyright', 'justifyblock'];
+        for (var n = 0; n < commands.length; n++) {
+          cmd = editor.getCommand(commands[n]);
+          cmd.contextSensitive = 1;
+          cmd.on('refresh', disableButtonIfOnWidget, null, null, 4);
+        }
+      }
+    }
+  });
+
+  /**
+   * Finds an element by its name.
+   *
+   * Function will check first the passed element itself and then all its
+   * children in DFS order.
+   *
+   * @param {CKEDITOR.htmlParser.element} element
+   *   The element to search.
+   * @param {string} name
+   *   The element name to search for.
+   *
+   * @return {?CKEDITOR.htmlParser.element}
+   *   The found element, or null.
+   */
+  function findElementByName(element, name) {
+    if (element.name === name) {
+      return element;
+    }
+
+    var found = null;
+    element.forEach(function (el) {
+      if (el.name === name) {
+        found = el;
+        // Stop here.
+        return false;
+      }
+    }, CKEDITOR.NODE_ELEMENT);
+    return found;
+  }
+
+})(CKEDITOR);
diff --git a/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.js b/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.js
index d1d666c5b945..4928c320a5e9 100644
--- a/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.js
+++ b/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.js
@@ -1,14 +1,10 @@
 /**
- * @file
- * Drupal Image Caption plugin.
- *
- * This alters the existing CKEditor image2 widget plugin, which is already
- * altered by the Drupal Image plugin, to:
- * - allow for the data-caption and data-align attributes to be set
- * - mimic the upcasting behavior of the caption_filter filter.
- *
- * @ignore
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/ckeditor/js/plugins/drupalimagecaption/plugin.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function (CKEDITOR) {
 
@@ -17,30 +13,20 @@
   CKEDITOR.plugins.add('drupalimagecaption', {
     requires: 'drupalimage',
 
-    beforeInit: function (editor) {
-      // Disable default placeholder text that comes with CKEditor's image2
-      // plugin: it has an inferior UX (it requires the user to manually delete
-      // the place holder text).
+    beforeInit: function beforeInit(editor) {
       editor.lang.image2.captionPlaceholder = '';
 
-      // Drupal.t() will not work inside CKEditor plugins because CKEditor loads
-      // the JavaScript file instead of Drupal. Pull translated strings from the
-      // plugin settings that are translated server-side.
       var placeholderText = editor.config.drupalImageCaption_captionPlaceholderText;
 
-      // Override the image2 widget definition to handle the additional
-      // data-align and data-caption attributes.
       editor.on('widgetDefinition', function (event) {
         var widgetDefinition = event.data;
         if (widgetDefinition.name !== 'image') {
           return;
         }
 
-        // Only perform the downcasting/upcasting for to the enabled filters.
         var captionFilterEnabled = editor.config.drupalImageCaption_captionFilterEnabled;
         var alignFilterEnabled = editor.config.drupalImageCaption_alignFilterEnabled;
 
-        // Override default features definitions for drupalimagecaption.
         CKEDITOR.tools.extend(widgetDefinition.features, {
           caption: {
             requiredContent: 'img[data-caption]'
@@ -50,10 +36,6 @@
           }
         }, true);
 
-        // Extend requiredContent & allowedContent.
-        // CKEDITOR.style is an immutable object: we cannot modify its
-        // definition to extend requiredContent. Hence we get the definition,
-        // modify it, and pass it to a new CKEDITOR.style instance.
         var requiredContent = widgetDefinition.requiredContent.getDefinition();
         requiredContent.attributes['data-align'] = '';
         requiredContent.attributes['data-caption'] = '';
@@ -61,15 +43,8 @@
         widgetDefinition.allowedContent.img.attributes['!data-align'] = true;
         widgetDefinition.allowedContent.img.attributes['!data-caption'] = true;
 
-        // Override allowedContent setting for the 'caption' nested editable.
-        // This must match what caption_filter enforces.
-        // @see \Drupal\filter\Plugin\Filter\FilterCaption::process()
-        // @see \Drupal\Component\Utility\Xss::filter()
         widgetDefinition.editables.caption.allowedContent = 'a[!href]; em strong cite code br';
 
-        // Override downcast(): ensure we *only* output <img>, but also ensure
-        // we include the data-entity-type, data-entity-uuid, data-align and
-        // data-caption attributes.
         var originalDowncast = widgetDefinition.downcast;
         widgetDefinition.downcast = function (element) {
           var img = findElementByName(element, 'img');
@@ -80,8 +55,6 @@
           var attrs = img.attributes;
 
           if (captionFilterEnabled) {
-            // If image contains a non-empty caption, serialize caption to the
-            // data-caption attribute.
             if (captionHtml) {
               attrs['data-caption'] = captionHtml;
             }
@@ -92,30 +65,20 @@
             }
           }
 
-          // If img is wrapped with a link, we want to return that link.
           if (img.parent.name === 'a') {
             return img.parent;
-          }
-          else {
+          } else {
             return img;
           }
         };
 
-        // We want to upcast <img> elements to a DOM structure required by the
-        // image2 widget. Depending on a case it may be:
-        //   - just an <img> tag (non-captioned, not-centered image),
-        //   - <img> tag in a paragraph (non-captioned, centered image),
-        //   - <figure> tag (captioned image).
-        // We take the same attributes into account as downcast() does.
         var originalUpcast = widgetDefinition.upcast;
         widgetDefinition.upcast = function (element, data) {
           if (element.name !== 'img' || !element.attributes['data-entity-type'] || !element.attributes['data-entity-uuid']) {
             return;
-          }
-          // Don't initialize on pasted fake objects.
-          else if (element.attributes['data-cke-realelement']) {
-            return;
-          }
+          } else if (element.attributes['data-cke-realelement']) {
+              return;
+            }
 
           element = originalUpcast.call(this, element, data);
           var attrs = element.attributes;
@@ -127,8 +90,6 @@
           var retElement = element;
           var caption;
 
-          // We won't need the attributes during editing: we'll use widget.data
-          // to store them (except the caption, which is stored in the DOM).
           if (captionFilterEnabled) {
             caption = attrs['data-caption'];
             delete attrs['data-caption'];
@@ -143,9 +104,6 @@
           delete attrs['data-entity-uuid'];
 
           if (captionFilterEnabled) {
-            // Unwrap from <p> wrapper created by HTML parser for a captioned
-            // image. The captioned image will be transformed to <figure>, so we
-            // don't want the <p> anymore.
             if (element.parent.name === 'p' && caption) {
               var index = element.getIndex();
               var splitBefore = index > 0;
@@ -163,15 +121,10 @@
               retElement = element;
             }
 
-            // If this image has a caption, create a full <figure> structure.
             if (caption) {
               var figure = new CKEDITOR.htmlParser.element('figure');
               caption = new CKEDITOR.htmlParser.fragment.fromHtml(caption, 'figcaption');
 
-              // Use Drupal's data-placeholder attribute to insert a CSS-based,
-              // translation-ready placeholder for empty captions. Note that it
-              // also must to be done for new instances (see
-              // widgetDefinition._createDialogSaveCallback).
               caption.attributes['data-placeholder'] = placeholderText;
 
               element.replaceWith(figure);
@@ -183,58 +136,37 @@
           }
 
           if (alignFilterEnabled) {
-            // If this image doesn't have a caption (or the caption filter is
-            // disabled), but it is centered, make sure that it's wrapped with
-            // <p>, which will become a part of the widget.
             if (data.align === 'center' && (!captionFilterEnabled || !caption)) {
               var p = new CKEDITOR.htmlParser.element('p');
               element.replaceWith(p);
               p.add(element);
-              // Apply the class for centered images.
+
               p.addClass(editor.config.image2_alignClasses[1]);
               retElement = p;
             }
           }
 
-          // Return the upcasted element (<img>, <figure> or <p>).
           return retElement;
         };
 
-        // Protected; keys of the widget data to be sent to the Drupal dialog.
-        // Append to the values defined by the drupalimage plugin.
-        // @see core/modules/ckeditor/js/plugins/drupalimage/plugin.js
         CKEDITOR.tools.extend(widgetDefinition._mapDataToDialog, {
           'align': 'data-align',
           'data-caption': 'data-caption',
           'hasCaption': 'hasCaption'
         });
 
-        // Override Drupal dialog save callback.
         var originalCreateDialogSaveCallback = widgetDefinition._createDialogSaveCallback;
         widgetDefinition._createDialogSaveCallback = function (editor, widget) {
           var saveCallback = originalCreateDialogSaveCallback.call(this, editor, widget);
 
           return function (dialogReturnValues) {
-            // Ensure hasCaption is a boolean. image2 assumes it always works
-            // with booleans; if this is not the case, then
-            // CKEDITOR.plugins.image2.stateShifter() will incorrectly mark
-            // widget.data.hasCaption as "changed" (e.g. when hasCaption === 0
-            // instead of hasCaption === false). This causes image2's "state
-            // shifter" to enter the wrong branch of the algorithm and blow up.
             dialogReturnValues.attributes.hasCaption = !!dialogReturnValues.attributes.hasCaption;
 
             var actualWidget = saveCallback(dialogReturnValues);
 
-            // By default, the template of captioned widget has no
-            // data-placeholder attribute. Note that it also must be done when
-            // upcasting existing elements (see widgetDefinition.upcast).
             if (dialogReturnValues.attributes.hasCaption) {
               actualWidget.editables.caption.setAttribute('data-placeholder', placeholderText);
 
-              // Some browsers will add a <br> tag to a newly created DOM
-              // element with no content. Remove this <br> if it is the only
-              // thing in the caption. Our placeholder support requires the
-              // element be entirely empty. See filter-caption.css.
               var captionElement = actualWidget.editables.caption.$;
               if (captionElement.childNodes.length === 1 && captionElement.childNodes.item(0).nodeName === 'BR') {
                 captionElement.removeChild(captionElement.childNodes.item(0));
@@ -242,12 +174,11 @@
             }
           };
         };
-      // Low priority to ensure drupalimage's event handler runs first.
       }, null, null, 20);
     },
 
-    afterInit: function (editor) {
-      var disableButtonIfOnWidget = function (evt) {
+    afterInit: function afterInit(editor) {
+      var disableButtonIfOnWidget = function disableButtonIfOnWidget(evt) {
         var widget = editor.widgets.focused;
         if (widget && widget.name === 'image') {
           this.setState(CKEDITOR.TRISTATE_DISABLED);
@@ -255,7 +186,6 @@
         }
       };
 
-      // Disable alignment buttons if the align filter is not enabled.
       if (editor.plugins.justify && !editor.config.drupalImageCaption_alignFilterEnabled) {
         var cmd;
         var commands = ['justifyleft', 'justifycenter', 'justifyright', 'justifyblock'];
@@ -268,20 +198,6 @@
     }
   });
 
-  /**
-   * Finds an element by its name.
-   *
-   * Function will check first the passed element itself and then all its
-   * children in DFS order.
-   *
-   * @param {CKEDITOR.htmlParser.element} element
-   *   The element to search.
-   * @param {string} name
-   *   The element name to search for.
-   *
-   * @return {?CKEDITOR.htmlParser.element}
-   *   The found element, or null.
-   */
   function findElementByName(element, name) {
     if (element.name === name) {
       return element;
@@ -291,11 +207,10 @@
     element.forEach(function (el) {
       if (el.name === name) {
         found = el;
-        // Stop here.
+
         return false;
       }
     }, CKEDITOR.NODE_ELEMENT);
     return found;
   }
-
-})(CKEDITOR);
+})(CKEDITOR);
\ No newline at end of file
diff --git a/core/modules/ckeditor/js/plugins/drupallink/plugin.es6.js b/core/modules/ckeditor/js/plugins/drupallink/plugin.es6.js
new file mode 100644
index 000000000000..9bd4dce98506
--- /dev/null
+++ b/core/modules/ckeditor/js/plugins/drupallink/plugin.es6.js
@@ -0,0 +1,304 @@
+/**
+ * @file
+ * Drupal Link plugin.
+ *
+ * @ignore
+ */
+
+(function ($, Drupal, drupalSettings, CKEDITOR) {
+
+  'use strict';
+
+  function parseAttributes(editor, element) {
+    var parsedAttributes = {};
+
+    var domElement = element.$;
+    var attribute;
+    var attributeName;
+    for (var attrIndex = 0; attrIndex < domElement.attributes.length; attrIndex++) {
+      attribute = domElement.attributes.item(attrIndex);
+      attributeName = attribute.nodeName.toLowerCase();
+      // Ignore data-cke-* attributes; they're CKEditor internals.
+      if (attributeName.indexOf('data-cke-') === 0) {
+        continue;
+      }
+      // Store the value for this attribute, unless there's a data-cke-saved-
+      // alternative for it, which will contain the quirk-free, original value.
+      parsedAttributes[attributeName] = element.data('cke-saved-' + attributeName) || attribute.nodeValue;
+    }
+
+    // Remove any cke_* classes.
+    if (parsedAttributes.class) {
+      parsedAttributes.class = CKEDITOR.tools.trim(parsedAttributes.class.replace(/cke_\S+/, ''));
+    }
+
+    return parsedAttributes;
+  }
+
+  function getAttributes(editor, data) {
+    var set = {};
+    for (var attributeName in data) {
+      if (data.hasOwnProperty(attributeName)) {
+        set[attributeName] = data[attributeName];
+      }
+    }
+
+    // CKEditor tracks the *actual* saved href in a data-cke-saved-* attribute
+    // to work around browser quirks. We need to update it.
+    set['data-cke-saved-href'] = set.href;
+
+    // Remove all attributes which are not currently set.
+    var removed = {};
+    for (var s in set) {
+      if (set.hasOwnProperty(s)) {
+        delete removed[s];
+      }
+    }
+
+    return {
+      set: set,
+      removed: CKEDITOR.tools.objectKeys(removed)
+    };
+  }
+
+  CKEDITOR.plugins.add('drupallink', {
+    icons: 'drupallink,drupalunlink',
+    hidpi: true,
+
+    init: function (editor) {
+      // Add the commands for link and unlink.
+      editor.addCommand('drupallink', {
+        allowedContent: {
+          a: {
+            attributes: {
+              '!href': true
+            },
+            classes: {}
+          }
+        },
+        requiredContent: new CKEDITOR.style({
+          element: 'a',
+          attributes: {
+            href: ''
+          }
+        }),
+        modes: {wysiwyg: 1},
+        canUndo: true,
+        exec: function (editor) {
+          var drupalImageUtils = CKEDITOR.plugins.drupalimage;
+          var focusedImageWidget = drupalImageUtils && drupalImageUtils.getFocusedWidget(editor);
+          var linkElement = getSelectedLink(editor);
+
+          // Set existing values based on selected element.
+          var existingValues = {};
+          if (linkElement && linkElement.$) {
+            existingValues = parseAttributes(editor, linkElement);
+          }
+          // Or, if an image widget is focused, we're editing a link wrapping
+          // an image widget.
+          else if (focusedImageWidget && focusedImageWidget.data.link) {
+            existingValues = CKEDITOR.tools.clone(focusedImageWidget.data.link);
+          }
+
+          // Prepare a save callback to be used upon saving the dialog.
+          var saveCallback = function (returnValues) {
+            // If an image widget is focused, we're not editing an independent
+            // link, but we're wrapping an image widget in a link.
+            if (focusedImageWidget) {
+              focusedImageWidget.setData('link', CKEDITOR.tools.extend(returnValues.attributes, focusedImageWidget.data.link));
+              editor.fire('saveSnapshot');
+              return;
+            }
+
+            editor.fire('saveSnapshot');
+
+            // Create a new link element if needed.
+            if (!linkElement && returnValues.attributes.href) {
+              var selection = editor.getSelection();
+              var range = selection.getRanges(1)[0];
+
+              // Use link URL as text with a collapsed cursor.
+              if (range.collapsed) {
+                // Shorten mailto URLs to just the email address.
+                var text = new CKEDITOR.dom.text(returnValues.attributes.href.replace(/^mailto:/, ''), editor.document);
+                range.insertNode(text);
+                range.selectNodeContents(text);
+              }
+
+              // Create the new link by applying a style to the new text.
+              var style = new CKEDITOR.style({element: 'a', attributes: returnValues.attributes});
+              style.type = CKEDITOR.STYLE_INLINE;
+              style.applyToRange(range);
+              range.select();
+
+              // Set the link so individual properties may be set below.
+              linkElement = getSelectedLink(editor);
+            }
+            // Update the link properties.
+            else if (linkElement) {
+              for (var attrName in returnValues.attributes) {
+                if (returnValues.attributes.hasOwnProperty(attrName)) {
+                  // Update the property if a value is specified.
+                  if (returnValues.attributes[attrName].length > 0) {
+                    var value = returnValues.attributes[attrName];
+                    linkElement.data('cke-saved-' + attrName, value);
+                    linkElement.setAttribute(attrName, value);
+                  }
+                  // Delete the property if set to an empty string.
+                  else {
+                    linkElement.removeAttribute(attrName);
+                  }
+                }
+              }
+            }
+
+            // Save snapshot for undo support.
+            editor.fire('saveSnapshot');
+          };
+          // Drupal.t() will not work inside CKEditor plugins because CKEditor
+          // loads the JavaScript file instead of Drupal. Pull translated
+          // strings from the plugin settings that are translated server-side.
+          var dialogSettings = {
+            title: linkElement ? editor.config.drupalLink_dialogTitleEdit : editor.config.drupalLink_dialogTitleAdd,
+            dialogClass: 'editor-link-dialog'
+          };
+
+          // Open the dialog for the edit form.
+          Drupal.ckeditor.openDialog(editor, Drupal.url('editor/dialog/link/' + editor.config.drupal.format), existingValues, saveCallback, dialogSettings);
+        }
+      });
+      editor.addCommand('drupalunlink', {
+        contextSensitive: 1,
+        startDisabled: 1,
+        requiredContent: new CKEDITOR.style({
+          element: 'a',
+          attributes: {
+            href: ''
+          }
+        }),
+        exec: function (editor) {
+          var style = new CKEDITOR.style({element: 'a', type: CKEDITOR.STYLE_INLINE, alwaysRemoveElement: 1});
+          editor.removeStyle(style);
+        },
+        refresh: function (editor, path) {
+          var element = path.lastElement && path.lastElement.getAscendant('a', true);
+          if (element && element.getName() === 'a' && element.getAttribute('href') && element.getChildCount()) {
+            this.setState(CKEDITOR.TRISTATE_OFF);
+          }
+          else {
+            this.setState(CKEDITOR.TRISTATE_DISABLED);
+          }
+        }
+      });
+
+      // CTRL + K.
+      editor.setKeystroke(CKEDITOR.CTRL + 75, 'drupallink');
+
+      // Add buttons for link and unlink.
+      if (editor.ui.addButton) {
+        editor.ui.addButton('DrupalLink', {
+          label: Drupal.t('Link'),
+          command: 'drupallink'
+        });
+        editor.ui.addButton('DrupalUnlink', {
+          label: Drupal.t('Unlink'),
+          command: 'drupalunlink'
+        });
+      }
+
+      editor.on('doubleclick', function (evt) {
+        var element = getSelectedLink(editor) || evt.data.element;
+
+        if (!element.isReadOnly()) {
+          if (element.is('a')) {
+            editor.getSelection().selectElement(element);
+            editor.getCommand('drupallink').exec();
+          }
+        }
+      });
+
+      // If the "menu" plugin is loaded, register the menu items.
+      if (editor.addMenuItems) {
+        editor.addMenuItems({
+          link: {
+            label: Drupal.t('Edit Link'),
+            command: 'drupallink',
+            group: 'link',
+            order: 1
+          },
+
+          unlink: {
+            label: Drupal.t('Unlink'),
+            command: 'drupalunlink',
+            group: 'link',
+            order: 5
+          }
+        });
+      }
+
+      // If the "contextmenu" plugin is loaded, register the listeners.
+      if (editor.contextMenu) {
+        editor.contextMenu.addListener(function (element, selection) {
+          if (!element || element.isReadOnly()) {
+            return null;
+          }
+          var anchor = getSelectedLink(editor);
+          if (!anchor) {
+            return null;
+          }
+
+          var menu = {};
+          if (anchor.getAttribute('href') && anchor.getChildCount()) {
+            menu = {link: CKEDITOR.TRISTATE_OFF, unlink: CKEDITOR.TRISTATE_OFF};
+          }
+          return menu;
+        });
+      }
+    }
+  });
+
+  /**
+   * Get the surrounding link element of current selection.
+   *
+   * The following selection will all return the link element.
+   *
+   * @example
+   *  <a href="#">li^nk</a>
+   *  <a href="#">[link]</a>
+   *  text[<a href="#">link]</a>
+   *  <a href="#">li[nk</a>]
+   *  [<b><a href="#">li]nk</a></b>]
+   *  [<a href="#"><b>li]nk</b></a>
+   *
+   * @param {CKEDITOR.editor} editor
+   *   The CKEditor editor object
+   *
+   * @return {?HTMLElement}
+   *   The selected link element, or null.
+   *
+   */
+  function getSelectedLink(editor) {
+    var selection = editor.getSelection();
+    var selectedElement = selection.getSelectedElement();
+    if (selectedElement && selectedElement.is('a')) {
+      return selectedElement;
+    }
+
+    var range = selection.getRanges(true)[0];
+
+    if (range) {
+      range.shrink(CKEDITOR.SHRINK_TEXT);
+      return editor.elementPath(range.getCommonAncestor()).contains('a', 1);
+    }
+    return null;
+  }
+
+  // Expose an API for other plugins to interact with drupallink widgets.
+  // (Compatible with the official CKEditor link plugin's API:
+  // http://dev.ckeditor.com/ticket/13885.)
+  CKEDITOR.plugins.drupallink = {
+    parseLinkAttributes: parseAttributes,
+    getLinkAttributes: getAttributes
+  };
+
+})(jQuery, Drupal, drupalSettings, CKEDITOR);
diff --git a/core/modules/ckeditor/js/plugins/drupallink/plugin.js b/core/modules/ckeditor/js/plugins/drupallink/plugin.js
index 9bd4dce98506..ee6ca366dd56 100644
--- a/core/modules/ckeditor/js/plugins/drupallink/plugin.js
+++ b/core/modules/ckeditor/js/plugins/drupallink/plugin.js
@@ -1,9 +1,10 @@
 /**
- * @file
- * Drupal Link plugin.
- *
- * @ignore
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/ckeditor/js/plugins/drupallink/plugin.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings, CKEDITOR) {
 
@@ -18,16 +19,14 @@
     for (var attrIndex = 0; attrIndex < domElement.attributes.length; attrIndex++) {
       attribute = domElement.attributes.item(attrIndex);
       attributeName = attribute.nodeName.toLowerCase();
-      // Ignore data-cke-* attributes; they're CKEditor internals.
+
       if (attributeName.indexOf('data-cke-') === 0) {
         continue;
       }
-      // Store the value for this attribute, unless there's a data-cke-saved-
-      // alternative for it, which will contain the quirk-free, original value.
+
       parsedAttributes[attributeName] = element.data('cke-saved-' + attributeName) || attribute.nodeValue;
     }
 
-    // Remove any cke_* classes.
     if (parsedAttributes.class) {
       parsedAttributes.class = CKEDITOR.tools.trim(parsedAttributes.class.replace(/cke_\S+/, ''));
     }
@@ -43,11 +42,8 @@
       }
     }
 
-    // CKEditor tracks the *actual* saved href in a data-cke-saved-* attribute
-    // to work around browser quirks. We need to update it.
     set['data-cke-saved-href'] = set.href;
 
-    // Remove all attributes which are not currently set.
     var removed = {};
     for (var s in set) {
       if (set.hasOwnProperty(s)) {
@@ -65,8 +61,7 @@
     icons: 'drupallink,drupalunlink',
     hidpi: true,
 
-    init: function (editor) {
-      // Add the commands for link and unlink.
+    init: function init(editor) {
       editor.addCommand('drupallink', {
         allowedContent: {
           a: {
@@ -82,28 +77,21 @@
             href: ''
           }
         }),
-        modes: {wysiwyg: 1},
+        modes: { wysiwyg: 1 },
         canUndo: true,
-        exec: function (editor) {
+        exec: function exec(editor) {
           var drupalImageUtils = CKEDITOR.plugins.drupalimage;
           var focusedImageWidget = drupalImageUtils && drupalImageUtils.getFocusedWidget(editor);
           var linkElement = getSelectedLink(editor);
 
-          // Set existing values based on selected element.
           var existingValues = {};
           if (linkElement && linkElement.$) {
             existingValues = parseAttributes(editor, linkElement);
-          }
-          // Or, if an image widget is focused, we're editing a link wrapping
-          // an image widget.
-          else if (focusedImageWidget && focusedImageWidget.data.link) {
-            existingValues = CKEDITOR.tools.clone(focusedImageWidget.data.link);
-          }
+          } else if (focusedImageWidget && focusedImageWidget.data.link) {
+              existingValues = CKEDITOR.tools.clone(focusedImageWidget.data.link);
+            }
 
-          // Prepare a save callback to be used upon saving the dialog.
-          var saveCallback = function (returnValues) {
-            // If an image widget is focused, we're not editing an independent
-            // link, but we're wrapping an image widget in a link.
+          var saveCallback = function saveCallback(returnValues) {
             if (focusedImageWidget) {
               focusedImageWidget.setData('link', CKEDITOR.tools.extend(returnValues.attributes, focusedImageWidget.data.link));
               editor.fire('saveSnapshot');
@@ -112,58 +100,44 @@
 
             editor.fire('saveSnapshot');
 
-            // Create a new link element if needed.
             if (!linkElement && returnValues.attributes.href) {
               var selection = editor.getSelection();
               var range = selection.getRanges(1)[0];
 
-              // Use link URL as text with a collapsed cursor.
               if (range.collapsed) {
-                // Shorten mailto URLs to just the email address.
                 var text = new CKEDITOR.dom.text(returnValues.attributes.href.replace(/^mailto:/, ''), editor.document);
                 range.insertNode(text);
                 range.selectNodeContents(text);
               }
 
-              // Create the new link by applying a style to the new text.
-              var style = new CKEDITOR.style({element: 'a', attributes: returnValues.attributes});
+              var style = new CKEDITOR.style({ element: 'a', attributes: returnValues.attributes });
               style.type = CKEDITOR.STYLE_INLINE;
               style.applyToRange(range);
               range.select();
 
-              // Set the link so individual properties may be set below.
               linkElement = getSelectedLink(editor);
-            }
-            // Update the link properties.
-            else if (linkElement) {
-              for (var attrName in returnValues.attributes) {
-                if (returnValues.attributes.hasOwnProperty(attrName)) {
-                  // Update the property if a value is specified.
-                  if (returnValues.attributes[attrName].length > 0) {
-                    var value = returnValues.attributes[attrName];
-                    linkElement.data('cke-saved-' + attrName, value);
-                    linkElement.setAttribute(attrName, value);
-                  }
-                  // Delete the property if set to an empty string.
-                  else {
-                    linkElement.removeAttribute(attrName);
+            } else if (linkElement) {
+                for (var attrName in returnValues.attributes) {
+                  if (returnValues.attributes.hasOwnProperty(attrName)) {
+                    if (returnValues.attributes[attrName].length > 0) {
+                      var value = returnValues.attributes[attrName];
+                      linkElement.data('cke-saved-' + attrName, value);
+                      linkElement.setAttribute(attrName, value);
+                    } else {
+                        linkElement.removeAttribute(attrName);
+                      }
                   }
                 }
               }
-            }
 
-            // Save snapshot for undo support.
             editor.fire('saveSnapshot');
           };
-          // Drupal.t() will not work inside CKEditor plugins because CKEditor
-          // loads the JavaScript file instead of Drupal. Pull translated
-          // strings from the plugin settings that are translated server-side.
+
           var dialogSettings = {
             title: linkElement ? editor.config.drupalLink_dialogTitleEdit : editor.config.drupalLink_dialogTitleAdd,
             dialogClass: 'editor-link-dialog'
           };
 
-          // Open the dialog for the edit form.
           Drupal.ckeditor.openDialog(editor, Drupal.url('editor/dialog/link/' + editor.config.drupal.format), existingValues, saveCallback, dialogSettings);
         }
       });
@@ -176,25 +150,22 @@
             href: ''
           }
         }),
-        exec: function (editor) {
-          var style = new CKEDITOR.style({element: 'a', type: CKEDITOR.STYLE_INLINE, alwaysRemoveElement: 1});
+        exec: function exec(editor) {
+          var style = new CKEDITOR.style({ element: 'a', type: CKEDITOR.STYLE_INLINE, alwaysRemoveElement: 1 });
           editor.removeStyle(style);
         },
-        refresh: function (editor, path) {
+        refresh: function refresh(editor, path) {
           var element = path.lastElement && path.lastElement.getAscendant('a', true);
           if (element && element.getName() === 'a' && element.getAttribute('href') && element.getChildCount()) {
             this.setState(CKEDITOR.TRISTATE_OFF);
-          }
-          else {
+          } else {
             this.setState(CKEDITOR.TRISTATE_DISABLED);
           }
         }
       });
 
-      // CTRL + K.
       editor.setKeystroke(CKEDITOR.CTRL + 75, 'drupallink');
 
-      // Add buttons for link and unlink.
       if (editor.ui.addButton) {
         editor.ui.addButton('DrupalLink', {
           label: Drupal.t('Link'),
@@ -217,7 +188,6 @@
         }
       });
 
-      // If the "menu" plugin is loaded, register the menu items.
       if (editor.addMenuItems) {
         editor.addMenuItems({
           link: {
@@ -236,7 +206,6 @@
         });
       }
 
-      // If the "contextmenu" plugin is loaded, register the listeners.
       if (editor.contextMenu) {
         editor.contextMenu.addListener(function (element, selection) {
           if (!element || element.isReadOnly()) {
@@ -249,7 +218,7 @@
 
           var menu = {};
           if (anchor.getAttribute('href') && anchor.getChildCount()) {
-            menu = {link: CKEDITOR.TRISTATE_OFF, unlink: CKEDITOR.TRISTATE_OFF};
+            menu = { link: CKEDITOR.TRISTATE_OFF, unlink: CKEDITOR.TRISTATE_OFF };
           }
           return menu;
         });
@@ -257,26 +226,6 @@
     }
   });
 
-  /**
-   * Get the surrounding link element of current selection.
-   *
-   * The following selection will all return the link element.
-   *
-   * @example
-   *  <a href="#">li^nk</a>
-   *  <a href="#">[link]</a>
-   *  text[<a href="#">link]</a>
-   *  <a href="#">li[nk</a>]
-   *  [<b><a href="#">li]nk</a></b>]
-   *  [<a href="#"><b>li]nk</b></a>
-   *
-   * @param {CKEDITOR.editor} editor
-   *   The CKEditor editor object
-   *
-   * @return {?HTMLElement}
-   *   The selected link element, or null.
-   *
-   */
   function getSelectedLink(editor) {
     var selection = editor.getSelection();
     var selectedElement = selection.getSelectedElement();
@@ -293,12 +242,8 @@
     return null;
   }
 
-  // Expose an API for other plugins to interact with drupallink widgets.
-  // (Compatible with the official CKEditor link plugin's API:
-  // http://dev.ckeditor.com/ticket/13885.)
   CKEDITOR.plugins.drupallink = {
     parseLinkAttributes: parseAttributes,
     getLinkAttributes: getAttributes
   };
-
-})(jQuery, Drupal, drupalSettings, CKEDITOR);
+})(jQuery, Drupal, drupalSettings, CKEDITOR);
\ No newline at end of file
diff --git a/core/modules/ckeditor/js/views/AuralView.es6.js b/core/modules/ckeditor/js/views/AuralView.es6.js
new file mode 100644
index 000000000000..0659eb2061c9
--- /dev/null
+++ b/core/modules/ckeditor/js/views/AuralView.es6.js
@@ -0,0 +1,233 @@
+/**
+ * @file
+ * A Backbone View that provides the aural view of CKEditor toolbar
+ * configuration.
+ */
+
+(function (Drupal, Backbone, $) {
+
+  'use strict';
+
+  Drupal.ckeditor.AuralView = Backbone.View.extend(/** @lends Drupal.ckeditor.AuralView# */{
+
+    /**
+     * @type {object}
+     */
+    events: {
+      'click .ckeditor-buttons a': 'announceButtonHelp',
+      'click .ckeditor-multiple-buttons a': 'announceSeparatorHelp',
+      'focus .ckeditor-button a': 'onFocus',
+      'focus .ckeditor-button-separator a': 'onFocus',
+      'focus .ckeditor-toolbar-group': 'onFocus'
+    },
+
+    /**
+     * Backbone View for CKEditor toolbar configuration; aural UX (output only).
+     *
+     * @constructs
+     *
+     * @augments Backbone.View
+     */
+    initialize: function () {
+      // Announce the button and group positions when the model is no longer
+      // dirty.
+      this.listenTo(this.model, 'change:isDirty', this.announceMove);
+    },
+
+    /**
+     * Calls announce on buttons and groups when their position is changed.
+     *
+     * @param {Drupal.ckeditor.ConfigurationModel} model
+     *   The ckeditor configuration model.
+     * @param {bool} isDirty
+     *   A model attribute that indicates if the changed toolbar configuration
+     *   has been stored or not.
+     */
+    announceMove: function (model, isDirty) {
+      // Announce the position of a button or group after the model has been
+      // updated.
+      if (!isDirty) {
+        var item = document.activeElement || null;
+        if (item) {
+          var $item = $(item);
+          if ($item.hasClass('ckeditor-toolbar-group')) {
+            this.announceButtonGroupPosition($item);
+          }
+          else if ($item.parent().hasClass('ckeditor-button')) {
+            this.announceButtonPosition($item.parent());
+          }
+        }
+      }
+    },
+
+    /**
+     * Handles the focus event of elements in the active and available toolbars.
+     *
+     * @param {jQuery.Event} event
+     *   The focus event that was triggered.
+     */
+    onFocus: function (event) {
+      event.stopPropagation();
+
+      var $originalTarget = $(event.target);
+      var $currentTarget = $(event.currentTarget);
+      var $parent = $currentTarget.parent();
+      if ($parent.hasClass('ckeditor-button') || $parent.hasClass('ckeditor-button-separator')) {
+        this.announceButtonPosition($currentTarget.parent());
+      }
+      else if ($originalTarget.attr('role') !== 'button' && $currentTarget.hasClass('ckeditor-toolbar-group')) {
+        this.announceButtonGroupPosition($currentTarget);
+      }
+    },
+
+    /**
+     * Announces the current position of a button group.
+     *
+     * @param {jQuery} $group
+     *   A jQuery set that contains an li element that wraps a group of buttons.
+     */
+    announceButtonGroupPosition: function ($group) {
+      var $groups = $group.parent().children();
+      var $row = $group.closest('.ckeditor-row');
+      var $rows = $row.parent().children();
+      var position = $groups.index($group) + 1;
+      var positionCount = $groups.not('.placeholder').length;
+      var row = $rows.index($row) + 1;
+      var rowCount = $rows.not('.placeholder').length;
+      var text = Drupal.t('@groupName button group in position @position of @positionCount in row @row of @rowCount.', {
+        '@groupName': $group.attr('data-drupal-ckeditor-toolbar-group-name'),
+        '@position': position,
+        '@positionCount': positionCount,
+        '@row': row,
+        '@rowCount': rowCount
+      });
+      // If this position is the first in the last row then tell the user that
+      // pressing the down arrow key will create a new row.
+      if (position === 1 && row === rowCount) {
+        text += '\n';
+        text += Drupal.t('Press the down arrow key to create a new row.');
+      }
+      Drupal.announce(text, 'assertive');
+    },
+
+    /**
+     * Announces current button position.
+     *
+     * @param {jQuery} $button
+     *   A jQuery set that contains an li element that wraps a button.
+     */
+    announceButtonPosition: function ($button) {
+      var $row = $button.closest('.ckeditor-row');
+      var $rows = $row.parent().children();
+      var $buttons = $button.closest('.ckeditor-buttons').children();
+      var $group = $button.closest('.ckeditor-toolbar-group');
+      var $groups = $group.parent().children();
+      var groupPosition = $groups.index($group) + 1;
+      var groupPositionCount = $groups.not('.placeholder').length;
+      var position = $buttons.index($button) + 1;
+      var positionCount = $buttons.length;
+      var row = $rows.index($row) + 1;
+      var rowCount = $rows.not('.placeholder').length;
+      // The name of the button separator is 'button separator' and its type
+      // is 'separator', so we do not want to print the type of this item,
+      // otherwise the UA will speak 'button separator separator'.
+      var type = ($button.attr('data-drupal-ckeditor-type') === 'separator') ? '' : Drupal.t('button');
+      var text;
+      // The button is located in the available button set.
+      if ($button.closest('.ckeditor-toolbar-disabled').length > 0) {
+        text = Drupal.t('@name @type.', {
+          '@name': $button.children().attr('aria-label'),
+          '@type': type
+        });
+        text += '\n' + Drupal.t('Press the down arrow key to activate.');
+
+        Drupal.announce(text, 'assertive');
+      }
+      // The button is in the active toolbar.
+      else if ($group.not('.placeholder').length === 1) {
+        text = Drupal.t('@name @type in position @position of @positionCount in @groupName button group in row @row of @rowCount.', {
+          '@name': $button.children().attr('aria-label'),
+          '@type': type,
+          '@position': position,
+          '@positionCount': positionCount,
+          '@groupName': $group.attr('data-drupal-ckeditor-toolbar-group-name'),
+          '@row': row,
+          '@rowCount': rowCount
+        });
+        // If this position is the first in the last row then tell the user that
+        // pressing the down arrow key will create a new row.
+        if (groupPosition === 1 && position === 1 && row === rowCount) {
+          text += '\n';
+          text += Drupal.t('Press the down arrow key to create a new button group in a new row.');
+        }
+        // If this position is the last one in this row then tell the user that
+        // moving the button to the next group will create a new group.
+        if (groupPosition === groupPositionCount && position === positionCount) {
+          text += '\n';
+          text += Drupal.t('This is the last group. Move the button forward to create a new group.');
+        }
+        Drupal.announce(text, 'assertive');
+      }
+    },
+
+    /**
+     * Provides help information when a button is clicked.
+     *
+     * @param {jQuery.Event} event
+     *   The click event for the button click.
+     */
+    announceButtonHelp: function (event) {
+      var $link = $(event.currentTarget);
+      var $button = $link.parent();
+      var enabled = $button.closest('.ckeditor-toolbar-active').length > 0;
+      var message;
+
+      if (enabled) {
+        message = Drupal.t('The "@name" button is currently enabled.', {
+          '@name': $link.attr('aria-label')
+        });
+        message += '\n' + Drupal.t('Use the keyboard arrow keys to change the position of this button.');
+        message += '\n' + Drupal.t('Press the up arrow key on the top row to disable the button.');
+      }
+      else {
+        message = Drupal.t('The "@name" button is currently disabled.', {
+          '@name': $link.attr('aria-label')
+        });
+        message += '\n' + Drupal.t('Use the down arrow key to move this button into the active toolbar.');
+      }
+      Drupal.announce(message);
+      event.preventDefault();
+    },
+
+    /**
+     * Provides help information when a separator is clicked.
+     *
+     * @param {jQuery.Event} event
+     *   The click event for the separator click.
+     */
+    announceSeparatorHelp: function (event) {
+      var $link = $(event.currentTarget);
+      var $button = $link.parent();
+      var enabled = $button.closest('.ckeditor-toolbar-active').length > 0;
+      var message;
+
+      if (enabled) {
+        message = Drupal.t('This @name is currently enabled.', {
+          '@name': $link.attr('aria-label')
+        });
+        message += '\n' + Drupal.t('Use the keyboard arrow keys to change the position of this separator.');
+      }
+      else {
+        message = Drupal.t('Separators are used to visually split individual buttons.');
+        message += '\n' + Drupal.t('This @name is currently disabled.', {
+          '@name': $link.attr('aria-label')
+        });
+        message += '\n' + Drupal.t('Use the down arrow key to move this separator into the active toolbar.');
+        message += '\n' + Drupal.t('You may add multiple separators to each button group.');
+      }
+      Drupal.announce(message);
+      event.preventDefault();
+    }
+  });
+
+})(Drupal, Backbone, jQuery);
diff --git a/core/modules/ckeditor/js/views/AuralView.js b/core/modules/ckeditor/js/views/AuralView.js
index 0659eb2061c9..38b838b14525 100644
--- a/core/modules/ckeditor/js/views/AuralView.js
+++ b/core/modules/ckeditor/js/views/AuralView.js
@@ -1,18 +1,16 @@
 /**
- * @file
- * A Backbone View that provides the aural view of CKEditor toolbar
- * configuration.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/ckeditor/js/views/AuralView.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function (Drupal, Backbone, $) {
 
   'use strict';
 
-  Drupal.ckeditor.AuralView = Backbone.View.extend(/** @lends Drupal.ckeditor.AuralView# */{
-
-    /**
-     * @type {object}
-     */
+  Drupal.ckeditor.AuralView = Backbone.View.extend({
     events: {
       'click .ckeditor-buttons a': 'announceButtonHelp',
       'click .ckeditor-multiple-buttons a': 'announceSeparatorHelp',
@@ -21,52 +19,25 @@
       'focus .ckeditor-toolbar-group': 'onFocus'
     },
 
-    /**
-     * Backbone View for CKEditor toolbar configuration; aural UX (output only).
-     *
-     * @constructs
-     *
-     * @augments Backbone.View
-     */
-    initialize: function () {
-      // Announce the button and group positions when the model is no longer
-      // dirty.
+    initialize: function initialize() {
       this.listenTo(this.model, 'change:isDirty', this.announceMove);
     },
 
-    /**
-     * Calls announce on buttons and groups when their position is changed.
-     *
-     * @param {Drupal.ckeditor.ConfigurationModel} model
-     *   The ckeditor configuration model.
-     * @param {bool} isDirty
-     *   A model attribute that indicates if the changed toolbar configuration
-     *   has been stored or not.
-     */
-    announceMove: function (model, isDirty) {
-      // Announce the position of a button or group after the model has been
-      // updated.
+    announceMove: function announceMove(model, isDirty) {
       if (!isDirty) {
         var item = document.activeElement || null;
         if (item) {
           var $item = $(item);
           if ($item.hasClass('ckeditor-toolbar-group')) {
             this.announceButtonGroupPosition($item);
-          }
-          else if ($item.parent().hasClass('ckeditor-button')) {
+          } else if ($item.parent().hasClass('ckeditor-button')) {
             this.announceButtonPosition($item.parent());
           }
         }
       }
     },
 
-    /**
-     * Handles the focus event of elements in the active and available toolbars.
-     *
-     * @param {jQuery.Event} event
-     *   The focus event that was triggered.
-     */
-    onFocus: function (event) {
+    onFocus: function onFocus(event) {
       event.stopPropagation();
 
       var $originalTarget = $(event.target);
@@ -74,19 +45,12 @@
       var $parent = $currentTarget.parent();
       if ($parent.hasClass('ckeditor-button') || $parent.hasClass('ckeditor-button-separator')) {
         this.announceButtonPosition($currentTarget.parent());
-      }
-      else if ($originalTarget.attr('role') !== 'button' && $currentTarget.hasClass('ckeditor-toolbar-group')) {
+      } else if ($originalTarget.attr('role') !== 'button' && $currentTarget.hasClass('ckeditor-toolbar-group')) {
         this.announceButtonGroupPosition($currentTarget);
       }
     },
 
-    /**
-     * Announces the current position of a button group.
-     *
-     * @param {jQuery} $group
-     *   A jQuery set that contains an li element that wraps a group of buttons.
-     */
-    announceButtonGroupPosition: function ($group) {
+    announceButtonGroupPosition: function announceButtonGroupPosition($group) {
       var $groups = $group.parent().children();
       var $row = $group.closest('.ckeditor-row');
       var $rows = $row.parent().children();
@@ -101,8 +65,7 @@
         '@row': row,
         '@rowCount': rowCount
       });
-      // If this position is the first in the last row then tell the user that
-      // pressing the down arrow key will create a new row.
+
       if (position === 1 && row === rowCount) {
         text += '\n';
         text += Drupal.t('Press the down arrow key to create a new row.');
@@ -110,13 +73,7 @@
       Drupal.announce(text, 'assertive');
     },
 
-    /**
-     * Announces current button position.
-     *
-     * @param {jQuery} $button
-     *   A jQuery set that contains an li element that wraps a button.
-     */
-    announceButtonPosition: function ($button) {
+    announceButtonPosition: function announceButtonPosition($button) {
       var $row = $button.closest('.ckeditor-row');
       var $rows = $row.parent().children();
       var $buttons = $button.closest('.ckeditor-buttons').children();
@@ -128,12 +85,10 @@
       var positionCount = $buttons.length;
       var row = $rows.index($row) + 1;
       var rowCount = $rows.not('.placeholder').length;
-      // The name of the button separator is 'button separator' and its type
-      // is 'separator', so we do not want to print the type of this item,
-      // otherwise the UA will speak 'button separator separator'.
-      var type = ($button.attr('data-drupal-ckeditor-type') === 'separator') ? '' : Drupal.t('button');
+
+      var type = $button.attr('data-drupal-ckeditor-type') === 'separator' ? '' : Drupal.t('button');
       var text;
-      // The button is located in the available button set.
+
       if ($button.closest('.ckeditor-toolbar-disabled').length > 0) {
         text = Drupal.t('@name @type.', {
           '@name': $button.children().attr('aria-label'),
@@ -142,41 +97,31 @@
         text += '\n' + Drupal.t('Press the down arrow key to activate.');
 
         Drupal.announce(text, 'assertive');
-      }
-      // The button is in the active toolbar.
-      else if ($group.not('.placeholder').length === 1) {
-        text = Drupal.t('@name @type in position @position of @positionCount in @groupName button group in row @row of @rowCount.', {
-          '@name': $button.children().attr('aria-label'),
-          '@type': type,
-          '@position': position,
-          '@positionCount': positionCount,
-          '@groupName': $group.attr('data-drupal-ckeditor-toolbar-group-name'),
-          '@row': row,
-          '@rowCount': rowCount
-        });
-        // If this position is the first in the last row then tell the user that
-        // pressing the down arrow key will create a new row.
-        if (groupPosition === 1 && position === 1 && row === rowCount) {
-          text += '\n';
-          text += Drupal.t('Press the down arrow key to create a new button group in a new row.');
-        }
-        // If this position is the last one in this row then tell the user that
-        // moving the button to the next group will create a new group.
-        if (groupPosition === groupPositionCount && position === positionCount) {
-          text += '\n';
-          text += Drupal.t('This is the last group. Move the button forward to create a new group.');
+      } else if ($group.not('.placeholder').length === 1) {
+          text = Drupal.t('@name @type in position @position of @positionCount in @groupName button group in row @row of @rowCount.', {
+            '@name': $button.children().attr('aria-label'),
+            '@type': type,
+            '@position': position,
+            '@positionCount': positionCount,
+            '@groupName': $group.attr('data-drupal-ckeditor-toolbar-group-name'),
+            '@row': row,
+            '@rowCount': rowCount
+          });
+
+          if (groupPosition === 1 && position === 1 && row === rowCount) {
+            text += '\n';
+            text += Drupal.t('Press the down arrow key to create a new button group in a new row.');
+          }
+
+          if (groupPosition === groupPositionCount && position === positionCount) {
+            text += '\n';
+            text += Drupal.t('This is the last group. Move the button forward to create a new group.');
+          }
+          Drupal.announce(text, 'assertive');
         }
-        Drupal.announce(text, 'assertive');
-      }
     },
 
-    /**
-     * Provides help information when a button is clicked.
-     *
-     * @param {jQuery.Event} event
-     *   The click event for the button click.
-     */
-    announceButtonHelp: function (event) {
+    announceButtonHelp: function announceButtonHelp(event) {
       var $link = $(event.currentTarget);
       var $button = $link.parent();
       var enabled = $button.closest('.ckeditor-toolbar-active').length > 0;
@@ -188,8 +133,7 @@
         });
         message += '\n' + Drupal.t('Use the keyboard arrow keys to change the position of this button.');
         message += '\n' + Drupal.t('Press the up arrow key on the top row to disable the button.');
-      }
-      else {
+      } else {
         message = Drupal.t('The "@name" button is currently disabled.', {
           '@name': $link.attr('aria-label')
         });
@@ -199,13 +143,7 @@
       event.preventDefault();
     },
 
-    /**
-     * Provides help information when a separator is clicked.
-     *
-     * @param {jQuery.Event} event
-     *   The click event for the separator click.
-     */
-    announceSeparatorHelp: function (event) {
+    announceSeparatorHelp: function announceSeparatorHelp(event) {
       var $link = $(event.currentTarget);
       var $button = $link.parent();
       var enabled = $button.closest('.ckeditor-toolbar-active').length > 0;
@@ -216,8 +154,7 @@
           '@name': $link.attr('aria-label')
         });
         message += '\n' + Drupal.t('Use the keyboard arrow keys to change the position of this separator.');
-      }
-      else {
+      } else {
         message = Drupal.t('Separators are used to visually split individual buttons.');
         message += '\n' + Drupal.t('This @name is currently disabled.', {
           '@name': $link.attr('aria-label')
@@ -229,5 +166,4 @@
       event.preventDefault();
     }
   });
-
-})(Drupal, Backbone, jQuery);
+})(Drupal, Backbone, jQuery);
\ No newline at end of file
diff --git a/core/modules/ckeditor/js/views/ControllerView.es6.js b/core/modules/ckeditor/js/views/ControllerView.es6.js
new file mode 100644
index 000000000000..0f48373a79f8
--- /dev/null
+++ b/core/modules/ckeditor/js/views/ControllerView.es6.js
@@ -0,0 +1,383 @@
+/**
+ * @file
+ * A Backbone View acting as a controller for CKEditor toolbar configuration.
+ */
+
+(function ($, Drupal, Backbone, CKEDITOR, _) {
+
+  'use strict';
+
+  Drupal.ckeditor.ControllerView = Backbone.View.extend(/** @lends Drupal.ckeditor.ControllerView# */{
+
+    /**
+     * @type {object}
+     */
+    events: {},
+
+    /**
+     * Backbone View acting as a controller for CKEditor toolbar configuration.
+     *
+     * @constructs
+     *
+     * @augments Backbone.View
+     */
+    initialize: function () {
+      this.getCKEditorFeatures(this.model.get('hiddenEditorConfig'), this.disableFeaturesDisallowedByFilters.bind(this));
+
+      // Push the active editor configuration to the textarea.
+      this.model.listenTo(this.model, 'change:activeEditorConfig', this.model.sync);
+      this.listenTo(this.model, 'change:isDirty', this.parseEditorDOM);
+    },
+
+    /**
+     * Converts the active toolbar DOM structure to an object representation.
+     *
+     * @param {Drupal.ckeditor.ConfigurationModel} model
+     *   The state model for the CKEditor configuration.
+     * @param {bool} isDirty
+     *   Tracks whether the active toolbar DOM structure has been changed.
+     *   isDirty is toggled back to false in this method.
+     * @param {object} options
+     *   An object that includes:
+     * @param {bool} [options.broadcast]
+     *   A flag that controls whether a CKEditorToolbarChanged event should be
+     *   fired for configuration changes.
+     *
+     * @fires event:CKEditorToolbarChanged
+     */
+    parseEditorDOM: function (model, isDirty, options) {
+      if (isDirty) {
+        var currentConfig = this.model.get('activeEditorConfig');
+
+        // Process the rows.
+        var rows = [];
+        this.$el
+          .find('.ckeditor-active-toolbar-configuration')
+          .children('.ckeditor-row').each(function () {
+            var groups = [];
+            // Process the button groups.
+            $(this).find('.ckeditor-toolbar-group').each(function () {
+              var $group = $(this);
+              var $buttons = $group.find('.ckeditor-button');
+              if ($buttons.length) {
+                var group = {
+                  name: $group.attr('data-drupal-ckeditor-toolbar-group-name'),
+                  items: []
+                };
+                $group.find('.ckeditor-button, .ckeditor-multiple-button').each(function () {
+                  group.items.push($(this).attr('data-drupal-ckeditor-button-name'));
+                });
+                groups.push(group);
+              }
+            });
+            if (groups.length) {
+              rows.push(groups);
+            }
+          });
+        this.model.set('activeEditorConfig', rows);
+        // Mark the model as clean. Whether or not the sync to the textfield
+        // occurs depends on the activeEditorConfig attribute firing a change
+        // event. The DOM has at least been processed and posted, so as far as
+        // the model is concerned, it is clean.
+        this.model.set('isDirty', false);
+
+        // Determine whether we should trigger an event.
+        if (options.broadcast !== false) {
+          var prev = this.getButtonList(currentConfig);
+          var next = this.getButtonList(rows);
+          if (prev.length !== next.length) {
+            this.$el
+              .find('.ckeditor-toolbar-active')
+              .trigger('CKEditorToolbarChanged', [
+                (prev.length < next.length) ? 'added' : 'removed',
+                _.difference(_.union(prev, next), _.intersection(prev, next))[0]
+              ]);
+          }
+        }
+      }
+    },
+
+    /**
+     * Asynchronously retrieve the metadata for all available CKEditor features.
+     *
+     * In order to get a list of all features needed by CKEditor, we create a
+     * hidden CKEditor instance, then check the CKEditor's "allowedContent"
+     * filter settings. Because creating an instance is expensive, a callback
+     * must be provided that will receive a hash of {@link Drupal.EditorFeature}
+     * features keyed by feature (button) name.
+     *
+     * @param {object} CKEditorConfig
+     *   An object that represents the configuration settings for a CKEditor
+     *   editor component.
+     * @param {function} callback
+     *   A function to invoke when the instanceReady event is fired by the
+     *   CKEditor object.
+     */
+    getCKEditorFeatures: function (CKEditorConfig, callback) {
+      var getProperties = function (CKEPropertiesList) {
+        return (_.isObject(CKEPropertiesList)) ? _.keys(CKEPropertiesList) : [];
+      };
+
+      var convertCKERulesToEditorFeature = function (feature, CKEFeatureRules) {
+        for (var i = 0; i < CKEFeatureRules.length; i++) {
+          var CKERule = CKEFeatureRules[i];
+          var rule = new Drupal.EditorFeatureHTMLRule();
+
+          // Tags.
+          var tags = getProperties(CKERule.elements);
+          rule.required.tags = (CKERule.propertiesOnly) ? [] : tags;
+          rule.allowed.tags = tags;
+          // Attributes.
+          rule.required.attributes = getProperties(CKERule.requiredAttributes);
+          rule.allowed.attributes = getProperties(CKERule.attributes);
+          // Styles.
+          rule.required.styles = getProperties(CKERule.requiredStyles);
+          rule.allowed.styles = getProperties(CKERule.styles);
+          // Classes.
+          rule.required.classes = getProperties(CKERule.requiredClasses);
+          rule.allowed.classes = getProperties(CKERule.classes);
+          // Raw.
+          rule.raw = CKERule;
+
+          feature.addHTMLRule(rule);
+        }
+      };
+
+      // Create hidden CKEditor with all features enabled, retrieve metadata.
+      // @see \Drupal\ckeditor\Plugin\Editor\CKEditor::buildConfigurationForm().
+      var hiddenCKEditorID = 'ckeditor-hidden';
+      if (CKEDITOR.instances[hiddenCKEditorID]) {
+        CKEDITOR.instances[hiddenCKEditorID].destroy(true);
+      }
+      // Load external plugins, if any.
+      var hiddenEditorConfig = this.model.get('hiddenEditorConfig');
+      if (hiddenEditorConfig.drupalExternalPlugins) {
+        var externalPlugins = hiddenEditorConfig.drupalExternalPlugins;
+        for (var pluginName in externalPlugins) {
+          if (externalPlugins.hasOwnProperty(pluginName)) {
+            CKEDITOR.plugins.addExternal(pluginName, externalPlugins[pluginName], '');
+          }
+        }
+      }
+      CKEDITOR.inline($('#' + hiddenCKEditorID).get(0), CKEditorConfig);
+
+      // Once the instance is ready, retrieve the allowedContent filter rules
+      // and convert them to Drupal.EditorFeature objects.
+      CKEDITOR.once('instanceReady', function (e) {
+        if (e.editor.name === hiddenCKEditorID) {
+          // First collect all CKEditor allowedContent rules.
+          var CKEFeatureRulesMap = {};
+          var rules = e.editor.filter.allowedContent;
+          var rule;
+          var name;
+          for (var i = 0; i < rules.length; i++) {
+            rule = rules[i];
+            name = rule.featureName || ':(';
+            if (!CKEFeatureRulesMap[name]) {
+              CKEFeatureRulesMap[name] = [];
+            }
+            CKEFeatureRulesMap[name].push(rule);
+          }
+
+          // Now convert these to Drupal.EditorFeature objects. And track which
+          // buttons are mapped to which features.
+          // @see getFeatureForButton()
+          var features = {};
+          var buttonsToFeatures = {};
+          for (var featureName in CKEFeatureRulesMap) {
+            if (CKEFeatureRulesMap.hasOwnProperty(featureName)) {
+              var feature = new Drupal.EditorFeature(featureName);
+              convertCKERulesToEditorFeature(feature, CKEFeatureRulesMap[featureName]);
+              features[featureName] = feature;
+              var command = e.editor.getCommand(featureName);
+              if (command) {
+                buttonsToFeatures[command.uiItems[0].name] = featureName;
+              }
+            }
+          }
+
+          callback(features, buttonsToFeatures);
+        }
+      });
+    },
+
+    /**
+     * Retrieves the feature for a given button from featuresMetadata. Returns
+     * false if the given button is in fact a divider.
+     *
+     * @param {string} button
+     *   The name of a CKEditor button.
+     *
+     * @return {object}
+     *   The feature metadata object for a button.
+     */
+    getFeatureForButton: function (button) {
+      // Return false if the button being added is a divider.
+      if (button === '-') {
+        return false;
+      }
+
+      // Get a Drupal.editorFeature object that contains all metadata for
+      // the feature that was just added or removed. Not every feature has
+      // such metadata.
+      var featureName = this.model.get('buttonsToFeatures')[button.toLowerCase()];
+      // Features without an associated command do not have a 'feature name' by
+      // default, so we use the lowercased button name instead.
+      if (!featureName) {
+        featureName = button.toLowerCase();
+      }
+      var featuresMetadata = this.model.get('featuresMetadata');
+      if (!featuresMetadata[featureName]) {
+        featuresMetadata[featureName] = new Drupal.EditorFeature(featureName);
+        this.model.set('featuresMetadata', featuresMetadata);
+      }
+      return featuresMetadata[featureName];
+    },
+
+    /**
+     * Checks buttons against filter settings; disables disallowed buttons.
+     *
+     * @param {object} features
+     *   A map of {@link Drupal.EditorFeature} objects.
+     * @param {object} buttonsToFeatures
+     *   Object containing the button-to-feature mapping.
+     *
+     * @see Drupal.ckeditor.ControllerView#getFeatureForButton
+     */
+    disableFeaturesDisallowedByFilters: function (features, buttonsToFeatures) {
+      this.model.set('featuresMetadata', features);
+      // Store the button-to-feature mapping. Needs to happen only once, because
+      // the same buttons continue to have the same features; only the rules for
+      // specific features may change.
+      // @see getFeatureForButton()
+      this.model.set('buttonsToFeatures', buttonsToFeatures);
+
+      // Ensure that toolbar configuration changes are broadcast.
+      this.broadcastConfigurationChanges(this.$el);
+
+      // Initialization: not all of the default toolbar buttons may be allowed
+      // by the current filter settings. Remove any of the default toolbar
+      // buttons that require more permissive filter settings. The remaining
+      // default toolbar buttons are marked as "added".
+      var existingButtons = [];
+      // Loop through each button group after flattening the groups from the
+      // toolbar row arrays.
+      var buttonGroups = _.flatten(this.model.get('activeEditorConfig'));
+      for (var i = 0; i < buttonGroups.length; i++) {
+        // Pull the button names from each toolbar button group.
+        var buttons = buttonGroups[i].items;
+        for (var k = 0; k < buttons.length; k++) {
+          existingButtons.push(buttons[k]);
+        }
+      }
+      // Remove duplicate buttons.
+      existingButtons = _.unique(existingButtons);
+      // Prepare the active toolbar and available-button toolbars.
+      for (var n = 0; n < existingButtons.length; n++) {
+        var button = existingButtons[n];
+        var feature = this.getFeatureForButton(button);
+        // Skip dividers.
+        if (feature === false) {
+          continue;
+        }
+
+        if (Drupal.editorConfiguration.featureIsAllowedByFilters(feature)) {
+          // Existing toolbar buttons are in fact "added features".
+          this.$el.find('.ckeditor-toolbar-active').trigger('CKEditorToolbarChanged', ['added', existingButtons[n]]);
+        }
+        else {
+          // Move the button element from the active the active toolbar to the
+          // list of available buttons.
+          $('.ckeditor-toolbar-active li[data-drupal-ckeditor-button-name="' + button + '"]')
+            .detach()
+            .appendTo('.ckeditor-toolbar-disabled > .ckeditor-toolbar-available > ul');
+          // Update the toolbar value field.
+          this.model.set({isDirty: true}, {broadcast: false});
+        }
+      }
+    },
+
+    /**
+     * Sets up broadcasting of CKEditor toolbar configuration changes.
+     *
+     * @param {jQuery} $ckeditorToolbar
+     *   The active toolbar DOM element wrapped in jQuery.
+     */
+    broadcastConfigurationChanges: function ($ckeditorToolbar) {
+      var view = this;
+      var hiddenEditorConfig = this.model.get('hiddenEditorConfig');
+      var getFeatureForButton = this.getFeatureForButton.bind(this);
+      var getCKEditorFeatures = this.getCKEditorFeatures.bind(this);
+      $ckeditorToolbar
+        .find('.ckeditor-toolbar-active')
+        // Listen for CKEditor toolbar configuration changes. When a button is
+        // added/removed, call an appropriate Drupal.editorConfiguration method.
+        .on('CKEditorToolbarChanged.ckeditorAdmin', function (event, action, button) {
+          var feature = getFeatureForButton(button);
+
+          // Early-return if the button being added is a divider.
+          if (feature === false) {
+            return;
+          }
+
+          // Trigger a standardized text editor configuration event to indicate
+          // whether a feature was added or removed, so that filters can react.
+          var configEvent = (action === 'added') ? 'addedFeature' : 'removedFeature';
+          Drupal.editorConfiguration[configEvent](feature);
+        })
+        // Listen for CKEditor plugin settings changes. When a plugin setting is
+        // changed, rebuild the CKEditor features metadata.
+        .on('CKEditorPluginSettingsChanged.ckeditorAdmin', function (event, settingsChanges) {
+          // Update hidden CKEditor configuration.
+          for (var key in settingsChanges) {
+            if (settingsChanges.hasOwnProperty(key)) {
+              hiddenEditorConfig[key] = settingsChanges[key];
+            }
+          }
+
+          // Retrieve features for the updated hidden CKEditor configuration.
+          getCKEditorFeatures(hiddenEditorConfig, function (features) {
+            // Trigger a standardized text editor configuration event for each
+            // feature that was modified by the configuration changes.
+            var featuresMetadata = view.model.get('featuresMetadata');
+            for (var name in features) {
+              if (features.hasOwnProperty(name)) {
+                var feature = features[name];
+                if (featuresMetadata.hasOwnProperty(name) && !_.isEqual(featuresMetadata[name], feature)) {
+                  Drupal.editorConfiguration.modifiedFeature(feature);
+                }
+              }
+            }
+            // Update the CKEditor features metadata.
+            view.model.set('featuresMetadata', features);
+          });
+        });
+    },
+
+    /**
+     * Returns the list of buttons from an editor configuration.
+     *
+     * @param {object} config
+     *   A CKEditor configuration object.
+     *
+     * @return {Array}
+     *   A list of buttons in the CKEditor configuration.
+     */
+    getButtonList: function (config) {
+      var buttons = [];
+      // Remove the rows.
+      config = _.flatten(config);
+
+      // Loop through the button groups and pull out the buttons.
+      config.forEach(function (group) {
+        group.items.forEach(function (button) {
+          buttons.push(button);
+        });
+      });
+
+      // Remove the dividing elements if any.
+      return _.without(buttons, '-');
+    }
+  });
+
+})(jQuery, Drupal, Backbone, CKEDITOR, _);
diff --git a/core/modules/ckeditor/js/views/ControllerView.js b/core/modules/ckeditor/js/views/ControllerView.js
index 0f48373a79f8..ab1e8bff8305 100644
--- a/core/modules/ckeditor/js/views/ControllerView.js
+++ b/core/modules/ckeditor/js/views/ControllerView.js
@@ -1,155 +1,99 @@
 /**
- * @file
- * A Backbone View acting as a controller for CKEditor toolbar configuration.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/ckeditor/js/views/ControllerView.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, Backbone, CKEDITOR, _) {
 
   'use strict';
 
-  Drupal.ckeditor.ControllerView = Backbone.View.extend(/** @lends Drupal.ckeditor.ControllerView# */{
-
-    /**
-     * @type {object}
-     */
+  Drupal.ckeditor.ControllerView = Backbone.View.extend({
     events: {},
 
-    /**
-     * Backbone View acting as a controller for CKEditor toolbar configuration.
-     *
-     * @constructs
-     *
-     * @augments Backbone.View
-     */
-    initialize: function () {
+    initialize: function initialize() {
       this.getCKEditorFeatures(this.model.get('hiddenEditorConfig'), this.disableFeaturesDisallowedByFilters.bind(this));
 
-      // Push the active editor configuration to the textarea.
       this.model.listenTo(this.model, 'change:activeEditorConfig', this.model.sync);
       this.listenTo(this.model, 'change:isDirty', this.parseEditorDOM);
     },
 
-    /**
-     * Converts the active toolbar DOM structure to an object representation.
-     *
-     * @param {Drupal.ckeditor.ConfigurationModel} model
-     *   The state model for the CKEditor configuration.
-     * @param {bool} isDirty
-     *   Tracks whether the active toolbar DOM structure has been changed.
-     *   isDirty is toggled back to false in this method.
-     * @param {object} options
-     *   An object that includes:
-     * @param {bool} [options.broadcast]
-     *   A flag that controls whether a CKEditorToolbarChanged event should be
-     *   fired for configuration changes.
-     *
-     * @fires event:CKEditorToolbarChanged
-     */
-    parseEditorDOM: function (model, isDirty, options) {
+    parseEditorDOM: function parseEditorDOM(model, isDirty, options) {
       if (isDirty) {
         var currentConfig = this.model.get('activeEditorConfig');
 
-        // Process the rows.
         var rows = [];
-        this.$el
-          .find('.ckeditor-active-toolbar-configuration')
-          .children('.ckeditor-row').each(function () {
-            var groups = [];
-            // Process the button groups.
-            $(this).find('.ckeditor-toolbar-group').each(function () {
-              var $group = $(this);
-              var $buttons = $group.find('.ckeditor-button');
-              if ($buttons.length) {
-                var group = {
-                  name: $group.attr('data-drupal-ckeditor-toolbar-group-name'),
-                  items: []
-                };
-                $group.find('.ckeditor-button, .ckeditor-multiple-button').each(function () {
-                  group.items.push($(this).attr('data-drupal-ckeditor-button-name'));
-                });
-                groups.push(group);
-              }
-            });
-            if (groups.length) {
-              rows.push(groups);
+        this.$el.find('.ckeditor-active-toolbar-configuration').children('.ckeditor-row').each(function () {
+          var groups = [];
+
+          $(this).find('.ckeditor-toolbar-group').each(function () {
+            var $group = $(this);
+            var $buttons = $group.find('.ckeditor-button');
+            if ($buttons.length) {
+              var group = {
+                name: $group.attr('data-drupal-ckeditor-toolbar-group-name'),
+                items: []
+              };
+              $group.find('.ckeditor-button, .ckeditor-multiple-button').each(function () {
+                group.items.push($(this).attr('data-drupal-ckeditor-button-name'));
+              });
+              groups.push(group);
             }
           });
+          if (groups.length) {
+            rows.push(groups);
+          }
+        });
         this.model.set('activeEditorConfig', rows);
-        // Mark the model as clean. Whether or not the sync to the textfield
-        // occurs depends on the activeEditorConfig attribute firing a change
-        // event. The DOM has at least been processed and posted, so as far as
-        // the model is concerned, it is clean.
+
         this.model.set('isDirty', false);
 
-        // Determine whether we should trigger an event.
         if (options.broadcast !== false) {
           var prev = this.getButtonList(currentConfig);
           var next = this.getButtonList(rows);
           if (prev.length !== next.length) {
-            this.$el
-              .find('.ckeditor-toolbar-active')
-              .trigger('CKEditorToolbarChanged', [
-                (prev.length < next.length) ? 'added' : 'removed',
-                _.difference(_.union(prev, next), _.intersection(prev, next))[0]
-              ]);
+            this.$el.find('.ckeditor-toolbar-active').trigger('CKEditorToolbarChanged', [prev.length < next.length ? 'added' : 'removed', _.difference(_.union(prev, next), _.intersection(prev, next))[0]]);
           }
         }
       }
     },
 
-    /**
-     * Asynchronously retrieve the metadata for all available CKEditor features.
-     *
-     * In order to get a list of all features needed by CKEditor, we create a
-     * hidden CKEditor instance, then check the CKEditor's "allowedContent"
-     * filter settings. Because creating an instance is expensive, a callback
-     * must be provided that will receive a hash of {@link Drupal.EditorFeature}
-     * features keyed by feature (button) name.
-     *
-     * @param {object} CKEditorConfig
-     *   An object that represents the configuration settings for a CKEditor
-     *   editor component.
-     * @param {function} callback
-     *   A function to invoke when the instanceReady event is fired by the
-     *   CKEditor object.
-     */
-    getCKEditorFeatures: function (CKEditorConfig, callback) {
-      var getProperties = function (CKEPropertiesList) {
-        return (_.isObject(CKEPropertiesList)) ? _.keys(CKEPropertiesList) : [];
+    getCKEditorFeatures: function getCKEditorFeatures(CKEditorConfig, callback) {
+      var getProperties = function getProperties(CKEPropertiesList) {
+        return _.isObject(CKEPropertiesList) ? _.keys(CKEPropertiesList) : [];
       };
 
-      var convertCKERulesToEditorFeature = function (feature, CKEFeatureRules) {
+      var convertCKERulesToEditorFeature = function convertCKERulesToEditorFeature(feature, CKEFeatureRules) {
         for (var i = 0; i < CKEFeatureRules.length; i++) {
           var CKERule = CKEFeatureRules[i];
           var rule = new Drupal.EditorFeatureHTMLRule();
 
-          // Tags.
           var tags = getProperties(CKERule.elements);
-          rule.required.tags = (CKERule.propertiesOnly) ? [] : tags;
+          rule.required.tags = CKERule.propertiesOnly ? [] : tags;
           rule.allowed.tags = tags;
-          // Attributes.
+
           rule.required.attributes = getProperties(CKERule.requiredAttributes);
           rule.allowed.attributes = getProperties(CKERule.attributes);
-          // Styles.
+
           rule.required.styles = getProperties(CKERule.requiredStyles);
           rule.allowed.styles = getProperties(CKERule.styles);
-          // Classes.
+
           rule.required.classes = getProperties(CKERule.requiredClasses);
           rule.allowed.classes = getProperties(CKERule.classes);
-          // Raw.
+
           rule.raw = CKERule;
 
           feature.addHTMLRule(rule);
         }
       };
 
-      // Create hidden CKEditor with all features enabled, retrieve metadata.
-      // @see \Drupal\ckeditor\Plugin\Editor\CKEditor::buildConfigurationForm().
       var hiddenCKEditorID = 'ckeditor-hidden';
       if (CKEDITOR.instances[hiddenCKEditorID]) {
         CKEDITOR.instances[hiddenCKEditorID].destroy(true);
       }
-      // Load external plugins, if any.
+
       var hiddenEditorConfig = this.model.get('hiddenEditorConfig');
       if (hiddenEditorConfig.drupalExternalPlugins) {
         var externalPlugins = hiddenEditorConfig.drupalExternalPlugins;
@@ -161,11 +105,8 @@
       }
       CKEDITOR.inline($('#' + hiddenCKEditorID).get(0), CKEditorConfig);
 
-      // Once the instance is ready, retrieve the allowedContent filter rules
-      // and convert them to Drupal.EditorFeature objects.
       CKEDITOR.once('instanceReady', function (e) {
         if (e.editor.name === hiddenCKEditorID) {
-          // First collect all CKEditor allowedContent rules.
           var CKEFeatureRulesMap = {};
           var rules = e.editor.filter.allowedContent;
           var rule;
@@ -179,9 +120,6 @@
             CKEFeatureRulesMap[name].push(rule);
           }
 
-          // Now convert these to Drupal.EditorFeature objects. And track which
-          // buttons are mapped to which features.
-          // @see getFeatureForButton()
           var features = {};
           var buttonsToFeatures = {};
           for (var featureName in CKEFeatureRulesMap) {
@@ -201,28 +139,13 @@
       });
     },
 
-    /**
-     * Retrieves the feature for a given button from featuresMetadata. Returns
-     * false if the given button is in fact a divider.
-     *
-     * @param {string} button
-     *   The name of a CKEditor button.
-     *
-     * @return {object}
-     *   The feature metadata object for a button.
-     */
-    getFeatureForButton: function (button) {
-      // Return false if the button being added is a divider.
+    getFeatureForButton: function getFeatureForButton(button) {
       if (button === '-') {
         return false;
       }
 
-      // Get a Drupal.editorFeature object that contains all metadata for
-      // the feature that was just added or removed. Not every feature has
-      // such metadata.
       var featureName = this.model.get('buttonsToFeatures')[button.toLowerCase()];
-      // Features without an associated command do not have a 'feature name' by
-      // default, so we use the lowercased button name instead.
+
       if (!featureName) {
         featureName = button.toLowerCase();
       }
@@ -234,150 +157,92 @@
       return featuresMetadata[featureName];
     },
 
-    /**
-     * Checks buttons against filter settings; disables disallowed buttons.
-     *
-     * @param {object} features
-     *   A map of {@link Drupal.EditorFeature} objects.
-     * @param {object} buttonsToFeatures
-     *   Object containing the button-to-feature mapping.
-     *
-     * @see Drupal.ckeditor.ControllerView#getFeatureForButton
-     */
-    disableFeaturesDisallowedByFilters: function (features, buttonsToFeatures) {
+    disableFeaturesDisallowedByFilters: function disableFeaturesDisallowedByFilters(features, buttonsToFeatures) {
       this.model.set('featuresMetadata', features);
-      // Store the button-to-feature mapping. Needs to happen only once, because
-      // the same buttons continue to have the same features; only the rules for
-      // specific features may change.
-      // @see getFeatureForButton()
+
       this.model.set('buttonsToFeatures', buttonsToFeatures);
 
-      // Ensure that toolbar configuration changes are broadcast.
       this.broadcastConfigurationChanges(this.$el);
 
-      // Initialization: not all of the default toolbar buttons may be allowed
-      // by the current filter settings. Remove any of the default toolbar
-      // buttons that require more permissive filter settings. The remaining
-      // default toolbar buttons are marked as "added".
       var existingButtons = [];
-      // Loop through each button group after flattening the groups from the
-      // toolbar row arrays.
+
       var buttonGroups = _.flatten(this.model.get('activeEditorConfig'));
       for (var i = 0; i < buttonGroups.length; i++) {
-        // Pull the button names from each toolbar button group.
         var buttons = buttonGroups[i].items;
         for (var k = 0; k < buttons.length; k++) {
           existingButtons.push(buttons[k]);
         }
       }
-      // Remove duplicate buttons.
+
       existingButtons = _.unique(existingButtons);
-      // Prepare the active toolbar and available-button toolbars.
+
       for (var n = 0; n < existingButtons.length; n++) {
         var button = existingButtons[n];
         var feature = this.getFeatureForButton(button);
-        // Skip dividers.
+
         if (feature === false) {
           continue;
         }
 
         if (Drupal.editorConfiguration.featureIsAllowedByFilters(feature)) {
-          // Existing toolbar buttons are in fact "added features".
           this.$el.find('.ckeditor-toolbar-active').trigger('CKEditorToolbarChanged', ['added', existingButtons[n]]);
-        }
-        else {
-          // Move the button element from the active the active toolbar to the
-          // list of available buttons.
-          $('.ckeditor-toolbar-active li[data-drupal-ckeditor-button-name="' + button + '"]')
-            .detach()
-            .appendTo('.ckeditor-toolbar-disabled > .ckeditor-toolbar-available > ul');
-          // Update the toolbar value field.
-          this.model.set({isDirty: true}, {broadcast: false});
+        } else {
+          $('.ckeditor-toolbar-active li[data-drupal-ckeditor-button-name="' + button + '"]').detach().appendTo('.ckeditor-toolbar-disabled > .ckeditor-toolbar-available > ul');
+
+          this.model.set({ isDirty: true }, { broadcast: false });
         }
       }
     },
 
-    /**
-     * Sets up broadcasting of CKEditor toolbar configuration changes.
-     *
-     * @param {jQuery} $ckeditorToolbar
-     *   The active toolbar DOM element wrapped in jQuery.
-     */
-    broadcastConfigurationChanges: function ($ckeditorToolbar) {
+    broadcastConfigurationChanges: function broadcastConfigurationChanges($ckeditorToolbar) {
       var view = this;
       var hiddenEditorConfig = this.model.get('hiddenEditorConfig');
       var getFeatureForButton = this.getFeatureForButton.bind(this);
       var getCKEditorFeatures = this.getCKEditorFeatures.bind(this);
-      $ckeditorToolbar
-        .find('.ckeditor-toolbar-active')
-        // Listen for CKEditor toolbar configuration changes. When a button is
-        // added/removed, call an appropriate Drupal.editorConfiguration method.
-        .on('CKEditorToolbarChanged.ckeditorAdmin', function (event, action, button) {
-          var feature = getFeatureForButton(button);
-
-          // Early-return if the button being added is a divider.
-          if (feature === false) {
-            return;
-          }
+      $ckeditorToolbar.find('.ckeditor-toolbar-active').on('CKEditorToolbarChanged.ckeditorAdmin', function (event, action, button) {
+        var feature = getFeatureForButton(button);
 
-          // Trigger a standardized text editor configuration event to indicate
-          // whether a feature was added or removed, so that filters can react.
-          var configEvent = (action === 'added') ? 'addedFeature' : 'removedFeature';
-          Drupal.editorConfiguration[configEvent](feature);
-        })
-        // Listen for CKEditor plugin settings changes. When a plugin setting is
-        // changed, rebuild the CKEditor features metadata.
-        .on('CKEditorPluginSettingsChanged.ckeditorAdmin', function (event, settingsChanges) {
-          // Update hidden CKEditor configuration.
-          for (var key in settingsChanges) {
-            if (settingsChanges.hasOwnProperty(key)) {
-              hiddenEditorConfig[key] = settingsChanges[key];
-            }
+        if (feature === false) {
+          return;
+        }
+
+        var configEvent = action === 'added' ? 'addedFeature' : 'removedFeature';
+        Drupal.editorConfiguration[configEvent](feature);
+      }).on('CKEditorPluginSettingsChanged.ckeditorAdmin', function (event, settingsChanges) {
+        for (var key in settingsChanges) {
+          if (settingsChanges.hasOwnProperty(key)) {
+            hiddenEditorConfig[key] = settingsChanges[key];
           }
+        }
 
-          // Retrieve features for the updated hidden CKEditor configuration.
-          getCKEditorFeatures(hiddenEditorConfig, function (features) {
-            // Trigger a standardized text editor configuration event for each
-            // feature that was modified by the configuration changes.
-            var featuresMetadata = view.model.get('featuresMetadata');
-            for (var name in features) {
-              if (features.hasOwnProperty(name)) {
-                var feature = features[name];
-                if (featuresMetadata.hasOwnProperty(name) && !_.isEqual(featuresMetadata[name], feature)) {
-                  Drupal.editorConfiguration.modifiedFeature(feature);
-                }
+        getCKEditorFeatures(hiddenEditorConfig, function (features) {
+          var featuresMetadata = view.model.get('featuresMetadata');
+          for (var name in features) {
+            if (features.hasOwnProperty(name)) {
+              var feature = features[name];
+              if (featuresMetadata.hasOwnProperty(name) && !_.isEqual(featuresMetadata[name], feature)) {
+                Drupal.editorConfiguration.modifiedFeature(feature);
               }
             }
-            // Update the CKEditor features metadata.
-            view.model.set('featuresMetadata', features);
-          });
+          }
+
+          view.model.set('featuresMetadata', features);
         });
+      });
     },
 
-    /**
-     * Returns the list of buttons from an editor configuration.
-     *
-     * @param {object} config
-     *   A CKEditor configuration object.
-     *
-     * @return {Array}
-     *   A list of buttons in the CKEditor configuration.
-     */
-    getButtonList: function (config) {
+    getButtonList: function getButtonList(config) {
       var buttons = [];
-      // Remove the rows.
+
       config = _.flatten(config);
 
-      // Loop through the button groups and pull out the buttons.
       config.forEach(function (group) {
         group.items.forEach(function (button) {
           buttons.push(button);
         });
       });
 
-      // Remove the dividing elements if any.
       return _.without(buttons, '-');
     }
   });
-
-})(jQuery, Drupal, Backbone, CKEDITOR, _);
+})(jQuery, Drupal, Backbone, CKEDITOR, _);
\ No newline at end of file
diff --git a/core/modules/ckeditor/js/views/KeyboardView.es6.js b/core/modules/ckeditor/js/views/KeyboardView.es6.js
new file mode 100644
index 000000000000..f44764e3aa83
--- /dev/null
+++ b/core/modules/ckeditor/js/views/KeyboardView.es6.js
@@ -0,0 +1,266 @@
+/**
+ * @file
+ * Backbone View providing the aural view of CKEditor keyboard UX configuration.
+ */
+
+(function ($, Drupal, Backbone, _) {
+
+  'use strict';
+
+  Drupal.ckeditor.KeyboardView = Backbone.View.extend(/** @lends Drupal.ckeditor.KeyboardView# */{
+
+    /**
+     * Backbone View for CKEditor toolbar configuration; keyboard UX.
+     *
+     * @constructs
+     *
+     * @augments Backbone.View
+     */
+    initialize: function () {
+      // Add keyboard arrow support.
+      this.$el.on('keydown.ckeditor', '.ckeditor-buttons a, .ckeditor-multiple-buttons a', this.onPressButton.bind(this));
+      this.$el.on('keydown.ckeditor', '[data-drupal-ckeditor-type="group"]', this.onPressGroup.bind(this));
+    },
+
+    /**
+     * @inheritdoc
+     */
+    render: function () {
+    },
+
+    /**
+     * Handles keypresses on a CKEditor configuration button.
+     *
+     * @param {jQuery.Event} event
+     *   The keypress event triggered.
+     */
+    onPressButton: function (event) {
+      var upDownKeys = [
+        38, // Up arrow.
+        63232, // Safari up arrow.
+        40, // Down arrow.
+        63233 // Safari down arrow.
+      ];
+      var leftRightKeys = [
+        37, // Left arrow.
+        63234, // Safari left arrow.
+        39, // Right arrow.
+        63235 // Safari right arrow.
+      ];
+
+      // Respond to an enter key press. Prevent the bubbling of the enter key
+      // press to the button group parent element.
+      if (event.keyCode === 13) {
+        event.stopPropagation();
+      }
+
+      // Only take action when a direction key is pressed.
+      if (_.indexOf(_.union(upDownKeys, leftRightKeys), event.keyCode) > -1) {
+        var view = this;
+        var $target = $(event.currentTarget);
+        var $button = $target.parent();
+        var $container = $button.parent();
+        var $group = $button.closest('.ckeditor-toolbar-group');
+        var $row;
+        var containerType = $container.data('drupal-ckeditor-button-sorting');
+        var $availableButtons = this.$el.find('[data-drupal-ckeditor-button-sorting="source"]');
+        var $activeButtons = this.$el.find('.ckeditor-toolbar-active');
+        // The current location of the button, just in case it needs to be put
+        // back.
+        var $originalGroup = $group;
+        var dir;
+
+        // Move available buttons between their container and the active
+        // toolbar.
+        if (containerType === 'source') {
+          // Move the button to the active toolbar configuration when the down
+          // or up keys are pressed.
+          if (_.indexOf([40, 63233], event.keyCode) > -1) {
+            // Move the button to the first row, first button group index
+            // position.
+            $activeButtons.find('.ckeditor-toolbar-group-buttons').eq(0).prepend($button);
+          }
+        }
+        else if (containerType === 'target') {
+          // Move buttons between sibling buttons in a group and between groups.
+          if (_.indexOf(leftRightKeys, event.keyCode) > -1) {
+            // Move left.
+            var $siblings = $container.children();
+            var index = $siblings.index($button);
+            if (_.indexOf([37, 63234], event.keyCode) > -1) {
+              // Move between sibling buttons.
+              if (index > 0) {
+                $button.insertBefore($container.children().eq(index - 1));
+              }
+              // Move between button groups and rows.
+              else {
+                // Move between button groups.
+                $group = $container.parent().prev();
+                if ($group.length > 0) {
+                  $group.find('.ckeditor-toolbar-group-buttons').append($button);
+                }
+                // Wrap between rows.
+                else {
+                  $container.closest('.ckeditor-row').prev().find('.ckeditor-toolbar-group').not('.placeholder').find('.ckeditor-toolbar-group-buttons').eq(-1).append($button);
+                }
+              }
+            }
+            // Move right.
+            else if (_.indexOf([39, 63235], event.keyCode) > -1) {
+              // Move between sibling buttons.
+              if (index < ($siblings.length - 1)) {
+                $button.insertAfter($container.children().eq(index + 1));
+              }
+              // Move between button groups. Moving right at the end of a row
+              // will create a new group.
+              else {
+                $container.parent().next().find('.ckeditor-toolbar-group-buttons').prepend($button);
+              }
+            }
+          }
+          // Move buttons between rows and the available button set.
+          else if (_.indexOf(upDownKeys, event.keyCode) > -1) {
+            dir = (_.indexOf([38, 63232], event.keyCode) > -1) ? 'prev' : 'next';
+            $row = $container.closest('.ckeditor-row')[dir]();
+            // Move the button back into the available button set.
+            if (dir === 'prev' && $row.length === 0) {
+              // If this is a divider, just destroy it.
+              if ($button.data('drupal-ckeditor-type') === 'separator') {
+                $button
+                  .off()
+                  .remove();
+                // Focus on the first button in the active toolbar.
+                $activeButtons.find('.ckeditor-toolbar-group-buttons').eq(0).children().eq(0).children().trigger('focus');
+              }
+              // Otherwise, move it.
+              else {
+                $availableButtons.prepend($button);
+              }
+            }
+            else {
+              $row.find('.ckeditor-toolbar-group-buttons').eq(0).prepend($button);
+            }
+          }
+        }
+        // Move dividers between their container and the active toolbar.
+        else if (containerType === 'dividers') {
+          // Move the button to the active toolbar configuration when the down
+          // or up keys are pressed.
+          if (_.indexOf([40, 63233], event.keyCode) > -1) {
+            // Move the button to the first row, first button group index
+            // position.
+            $button = $button.clone(true);
+            $activeButtons.find('.ckeditor-toolbar-group-buttons').eq(0).prepend($button);
+            $target = $button.children();
+          }
+        }
+
+        view = this;
+        // Attempt to move the button to the new toolbar position.
+        Drupal.ckeditor.registerButtonMove(this, $button, function (result) {
+
+          // Put the button back if the registration failed.
+          // If the button was in a row, then it was in the active toolbar
+          // configuration. The button was probably placed in a new group, but
+          // that action was canceled.
+          if (!result && $originalGroup) {
+            $originalGroup.find('.ckeditor-buttons').append($button);
+          }
+          // Otherwise refresh the sortables to acknowledge the new button
+          // positions.
+          else {
+            view.$el.find('.ui-sortable').sortable('refresh');
+          }
+          // Refocus the target button so that the user can continue from a
+          // known place.
+          $target.trigger('focus');
+        });
+
+        event.preventDefault();
+        event.stopPropagation();
+      }
+    },
+
+    /**
+     * Handles keypresses on a CKEditor configuration group.
+     *
+     * @param {jQuery.Event} event
+     *   The keypress event triggered.
+     */
+    onPressGroup: function (event) {
+      var upDownKeys = [
+        38, // Up arrow.
+        63232, // Safari up arrow.
+        40, // Down arrow.
+        63233 // Safari down arrow.
+      ];
+      var leftRightKeys = [
+        37, // Left arrow.
+        63234, // Safari left arrow.
+        39, // Right arrow.
+        63235 // Safari right arrow.
+      ];
+
+      // Respond to an enter key press.
+      if (event.keyCode === 13) {
+        var view = this;
+        // Open the group renaming dialog in the next evaluation cycle so that
+        // this event can be cancelled and the bubbling wiped out. Otherwise,
+        // Firefox has issues because the page focus is shifted to the dialog
+        // along with the keydown event.
+        window.setTimeout(function () {
+          Drupal.ckeditor.openGroupNameDialog(view, $(event.currentTarget));
+        }, 0);
+        event.preventDefault();
+        event.stopPropagation();
+      }
+
+      // Respond to direction key presses.
+      if (_.indexOf(_.union(upDownKeys, leftRightKeys), event.keyCode) > -1) {
+        var $group = $(event.currentTarget);
+        var $container = $group.parent();
+        var $siblings = $container.children();
+        var index;
+        var dir;
+        // Move groups between sibling groups.
+        if (_.indexOf(leftRightKeys, event.keyCode) > -1) {
+          index = $siblings.index($group);
+          // Move left between sibling groups.
+          if ((_.indexOf([37, 63234], event.keyCode) > -1)) {
+            if (index > 0) {
+              $group.insertBefore($siblings.eq(index - 1));
+            }
+            // Wrap between rows. Insert the group before the placeholder group
+            // at the end of the previous row.
+            else {
+              $group.insertBefore($container.closest('.ckeditor-row').prev().find('.ckeditor-toolbar-groups').children().eq(-1));
+            }
+          }
+          // Move right between sibling groups.
+          else if (_.indexOf([39, 63235], event.keyCode) > -1) {
+            // Move to the right if the next group is not a placeholder.
+            if (!$siblings.eq(index + 1).hasClass('placeholder')) {
+              $group.insertAfter($container.children().eq(index + 1));
+            }
+            // Wrap group between rows.
+            else {
+              $container.closest('.ckeditor-row').next().find('.ckeditor-toolbar-groups').prepend($group);
+            }
+          }
+
+        }
+        // Move groups between rows.
+        else if (_.indexOf(upDownKeys, event.keyCode) > -1) {
+          dir = (_.indexOf([38, 63232], event.keyCode) > -1) ? 'prev' : 'next';
+          $group.closest('.ckeditor-row')[dir]().find('.ckeditor-toolbar-groups').eq(0).prepend($group);
+        }
+
+        Drupal.ckeditor.registerGroupMove(this, $group);
+        $group.trigger('focus');
+        event.preventDefault();
+        event.stopPropagation();
+      }
+    }
+  });
+
+})(jQuery, Drupal, Backbone, _);
diff --git a/core/modules/ckeditor/js/views/KeyboardView.js b/core/modules/ckeditor/js/views/KeyboardView.js
index f44764e3aa83..9c124048f585 100644
--- a/core/modules/ckeditor/js/views/KeyboardView.js
+++ b/core/modules/ckeditor/js/views/KeyboardView.js
@@ -1,60 +1,31 @@
 /**
- * @file
- * Backbone View providing the aural view of CKEditor keyboard UX configuration.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/ckeditor/js/views/KeyboardView.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, Backbone, _) {
 
   'use strict';
 
-  Drupal.ckeditor.KeyboardView = Backbone.View.extend(/** @lends Drupal.ckeditor.KeyboardView# */{
-
-    /**
-     * Backbone View for CKEditor toolbar configuration; keyboard UX.
-     *
-     * @constructs
-     *
-     * @augments Backbone.View
-     */
-    initialize: function () {
-      // Add keyboard arrow support.
+  Drupal.ckeditor.KeyboardView = Backbone.View.extend({
+    initialize: function initialize() {
       this.$el.on('keydown.ckeditor', '.ckeditor-buttons a, .ckeditor-multiple-buttons a', this.onPressButton.bind(this));
       this.$el.on('keydown.ckeditor', '[data-drupal-ckeditor-type="group"]', this.onPressGroup.bind(this));
     },
 
-    /**
-     * @inheritdoc
-     */
-    render: function () {
-    },
+    render: function render() {},
 
-    /**
-     * Handles keypresses on a CKEditor configuration button.
-     *
-     * @param {jQuery.Event} event
-     *   The keypress event triggered.
-     */
-    onPressButton: function (event) {
-      var upDownKeys = [
-        38, // Up arrow.
-        63232, // Safari up arrow.
-        40, // Down arrow.
-        63233 // Safari down arrow.
-      ];
-      var leftRightKeys = [
-        37, // Left arrow.
-        63234, // Safari left arrow.
-        39, // Right arrow.
-        63235 // Safari right arrow.
-      ];
+    onPressButton: function onPressButton(event) {
+      var upDownKeys = [38, 63232, 40, 63233];
+      var leftRightKeys = [37, 63234, 39, 63235];
 
-      // Respond to an enter key press. Prevent the bubbling of the enter key
-      // press to the button group parent element.
       if (event.keyCode === 13) {
         event.stopPropagation();
       }
 
-      // Only take action when a direction key is pressed.
       if (_.indexOf(_.union(upDownKeys, leftRightKeys), event.keyCode) > -1) {
         var view = this;
         var $target = $(event.currentTarget);
@@ -65,114 +36,69 @@
         var containerType = $container.data('drupal-ckeditor-button-sorting');
         var $availableButtons = this.$el.find('[data-drupal-ckeditor-button-sorting="source"]');
         var $activeButtons = this.$el.find('.ckeditor-toolbar-active');
-        // The current location of the button, just in case it needs to be put
-        // back.
+
         var $originalGroup = $group;
         var dir;
 
-        // Move available buttons between their container and the active
-        // toolbar.
         if (containerType === 'source') {
-          // Move the button to the active toolbar configuration when the down
-          // or up keys are pressed.
           if (_.indexOf([40, 63233], event.keyCode) > -1) {
-            // Move the button to the first row, first button group index
-            // position.
             $activeButtons.find('.ckeditor-toolbar-group-buttons').eq(0).prepend($button);
           }
-        }
-        else if (containerType === 'target') {
-          // Move buttons between sibling buttons in a group and between groups.
+        } else if (containerType === 'target') {
           if (_.indexOf(leftRightKeys, event.keyCode) > -1) {
-            // Move left.
             var $siblings = $container.children();
             var index = $siblings.index($button);
             if (_.indexOf([37, 63234], event.keyCode) > -1) {
-              // Move between sibling buttons.
               if (index > 0) {
                 $button.insertBefore($container.children().eq(index - 1));
-              }
-              // Move between button groups and rows.
-              else {
-                // Move between button groups.
-                $group = $container.parent().prev();
-                if ($group.length > 0) {
-                  $group.find('.ckeditor-toolbar-group-buttons').append($button);
+              } else {
+                  $group = $container.parent().prev();
+                  if ($group.length > 0) {
+                    $group.find('.ckeditor-toolbar-group-buttons').append($button);
+                  } else {
+                      $container.closest('.ckeditor-row').prev().find('.ckeditor-toolbar-group').not('.placeholder').find('.ckeditor-toolbar-group-buttons').eq(-1).append($button);
+                    }
                 }
-                // Wrap between rows.
-                else {
-                  $container.closest('.ckeditor-row').prev().find('.ckeditor-toolbar-group').not('.placeholder').find('.ckeditor-toolbar-group-buttons').eq(-1).append($button);
-                }
-              }
-            }
-            // Move right.
-            else if (_.indexOf([39, 63235], event.keyCode) > -1) {
-              // Move between sibling buttons.
-              if (index < ($siblings.length - 1)) {
-                $button.insertAfter($container.children().eq(index + 1));
-              }
-              // Move between button groups. Moving right at the end of a row
-              // will create a new group.
-              else {
-                $container.parent().next().find('.ckeditor-toolbar-group-buttons').prepend($button);
+            } else if (_.indexOf([39, 63235], event.keyCode) > -1) {
+                if (index < $siblings.length - 1) {
+                  $button.insertAfter($container.children().eq(index + 1));
+                } else {
+                    $container.parent().next().find('.ckeditor-toolbar-group-buttons').prepend($button);
+                  }
               }
-            }
-          }
-          // Move buttons between rows and the available button set.
-          else if (_.indexOf(upDownKeys, event.keyCode) > -1) {
-            dir = (_.indexOf([38, 63232], event.keyCode) > -1) ? 'prev' : 'next';
-            $row = $container.closest('.ckeditor-row')[dir]();
-            // Move the button back into the available button set.
-            if (dir === 'prev' && $row.length === 0) {
-              // If this is a divider, just destroy it.
-              if ($button.data('drupal-ckeditor-type') === 'separator') {
-                $button
-                  .off()
-                  .remove();
-                // Focus on the first button in the active toolbar.
-                $activeButtons.find('.ckeditor-toolbar-group-buttons').eq(0).children().eq(0).children().trigger('focus');
-              }
-              // Otherwise, move it.
-              else {
-                $availableButtons.prepend($button);
+          } else if (_.indexOf(upDownKeys, event.keyCode) > -1) {
+              dir = _.indexOf([38, 63232], event.keyCode) > -1 ? 'prev' : 'next';
+              $row = $container.closest('.ckeditor-row')[dir]();
+
+              if (dir === 'prev' && $row.length === 0) {
+                if ($button.data('drupal-ckeditor-type') === 'separator') {
+                  $button.off().remove();
+
+                  $activeButtons.find('.ckeditor-toolbar-group-buttons').eq(0).children().eq(0).children().trigger('focus');
+                } else {
+                    $availableButtons.prepend($button);
+                  }
+              } else {
+                $row.find('.ckeditor-toolbar-group-buttons').eq(0).prepend($button);
               }
             }
-            else {
-              $row.find('.ckeditor-toolbar-group-buttons').eq(0).prepend($button);
+        } else if (containerType === 'dividers') {
+            if (_.indexOf([40, 63233], event.keyCode) > -1) {
+              $button = $button.clone(true);
+              $activeButtons.find('.ckeditor-toolbar-group-buttons').eq(0).prepend($button);
+              $target = $button.children();
             }
           }
-        }
-        // Move dividers between their container and the active toolbar.
-        else if (containerType === 'dividers') {
-          // Move the button to the active toolbar configuration when the down
-          // or up keys are pressed.
-          if (_.indexOf([40, 63233], event.keyCode) > -1) {
-            // Move the button to the first row, first button group index
-            // position.
-            $button = $button.clone(true);
-            $activeButtons.find('.ckeditor-toolbar-group-buttons').eq(0).prepend($button);
-            $target = $button.children();
-          }
-        }
 
         view = this;
-        // Attempt to move the button to the new toolbar position.
-        Drupal.ckeditor.registerButtonMove(this, $button, function (result) {
 
-          // Put the button back if the registration failed.
-          // If the button was in a row, then it was in the active toolbar
-          // configuration. The button was probably placed in a new group, but
-          // that action was canceled.
+        Drupal.ckeditor.registerButtonMove(this, $button, function (result) {
           if (!result && $originalGroup) {
             $originalGroup.find('.ckeditor-buttons').append($button);
-          }
-          // Otherwise refresh the sortables to acknowledge the new button
-          // positions.
-          else {
-            view.$el.find('.ui-sortable').sortable('refresh');
-          }
-          // Refocus the target button so that the user can continue from a
-          // known place.
+          } else {
+              view.$el.find('.ui-sortable').sortable('refresh');
+            }
+
           $target.trigger('focus');
         });
 
@@ -181,33 +107,13 @@
       }
     },
 
-    /**
-     * Handles keypresses on a CKEditor configuration group.
-     *
-     * @param {jQuery.Event} event
-     *   The keypress event triggered.
-     */
-    onPressGroup: function (event) {
-      var upDownKeys = [
-        38, // Up arrow.
-        63232, // Safari up arrow.
-        40, // Down arrow.
-        63233 // Safari down arrow.
-      ];
-      var leftRightKeys = [
-        37, // Left arrow.
-        63234, // Safari left arrow.
-        39, // Right arrow.
-        63235 // Safari right arrow.
-      ];
+    onPressGroup: function onPressGroup(event) {
+      var upDownKeys = [38, 63232, 40, 63233];
+      var leftRightKeys = [37, 63234, 39, 63235];
 
-      // Respond to an enter key press.
       if (event.keyCode === 13) {
         var view = this;
-        // Open the group renaming dialog in the next evaluation cycle so that
-        // this event can be cancelled and the bubbling wiped out. Otherwise,
-        // Firefox has issues because the page focus is shifted to the dialog
-        // along with the keydown event.
+
         window.setTimeout(function () {
           Drupal.ckeditor.openGroupNameDialog(view, $(event.currentTarget));
         }, 0);
@@ -215,46 +121,34 @@
         event.stopPropagation();
       }
 
-      // Respond to direction key presses.
       if (_.indexOf(_.union(upDownKeys, leftRightKeys), event.keyCode) > -1) {
         var $group = $(event.currentTarget);
         var $container = $group.parent();
         var $siblings = $container.children();
         var index;
         var dir;
-        // Move groups between sibling groups.
+
         if (_.indexOf(leftRightKeys, event.keyCode) > -1) {
           index = $siblings.index($group);
-          // Move left between sibling groups.
-          if ((_.indexOf([37, 63234], event.keyCode) > -1)) {
+
+          if (_.indexOf([37, 63234], event.keyCode) > -1) {
             if (index > 0) {
               $group.insertBefore($siblings.eq(index - 1));
+            } else {
+                $group.insertBefore($container.closest('.ckeditor-row').prev().find('.ckeditor-toolbar-groups').children().eq(-1));
+              }
+          } else if (_.indexOf([39, 63235], event.keyCode) > -1) {
+              if (!$siblings.eq(index + 1).hasClass('placeholder')) {
+                $group.insertAfter($container.children().eq(index + 1));
+              } else {
+                  $container.closest('.ckeditor-row').next().find('.ckeditor-toolbar-groups').prepend($group);
+                }
             }
-            // Wrap between rows. Insert the group before the placeholder group
-            // at the end of the previous row.
-            else {
-              $group.insertBefore($container.closest('.ckeditor-row').prev().find('.ckeditor-toolbar-groups').children().eq(-1));
-            }
-          }
-          // Move right between sibling groups.
-          else if (_.indexOf([39, 63235], event.keyCode) > -1) {
-            // Move to the right if the next group is not a placeholder.
-            if (!$siblings.eq(index + 1).hasClass('placeholder')) {
-              $group.insertAfter($container.children().eq(index + 1));
-            }
-            // Wrap group between rows.
-            else {
-              $container.closest('.ckeditor-row').next().find('.ckeditor-toolbar-groups').prepend($group);
-            }
+        } else if (_.indexOf(upDownKeys, event.keyCode) > -1) {
+            dir = _.indexOf([38, 63232], event.keyCode) > -1 ? 'prev' : 'next';
+            $group.closest('.ckeditor-row')[dir]().find('.ckeditor-toolbar-groups').eq(0).prepend($group);
           }
 
-        }
-        // Move groups between rows.
-        else if (_.indexOf(upDownKeys, event.keyCode) > -1) {
-          dir = (_.indexOf([38, 63232], event.keyCode) > -1) ? 'prev' : 'next';
-          $group.closest('.ckeditor-row')[dir]().find('.ckeditor-toolbar-groups').eq(0).prepend($group);
-        }
-
         Drupal.ckeditor.registerGroupMove(this, $group);
         $group.trigger('focus');
         event.preventDefault();
@@ -262,5 +156,4 @@
       }
     }
   });
-
-})(jQuery, Drupal, Backbone, _);
+})(jQuery, Drupal, Backbone, _);
\ No newline at end of file
diff --git a/core/modules/ckeditor/js/views/VisualView.es6.js b/core/modules/ckeditor/js/views/VisualView.es6.js
new file mode 100644
index 000000000000..2d8042ebbf68
--- /dev/null
+++ b/core/modules/ckeditor/js/views/VisualView.es6.js
@@ -0,0 +1,273 @@
+/**
+ * @file
+ * A Backbone View that provides the visual UX view of CKEditor toolbar
+ *   configuration.
+ */
+
+(function (Drupal, Backbone, $) {
+
+  'use strict';
+
+  Drupal.ckeditor.VisualView = Backbone.View.extend(/** @lends Drupal.ckeditor.VisualView# */{
+
+    events: {
+      'click .ckeditor-toolbar-group-name': 'onGroupNameClick',
+      'click .ckeditor-groupnames-toggle': 'onGroupNamesToggleClick',
+      'click .ckeditor-add-new-group button': 'onAddGroupButtonClick'
+    },
+
+    /**
+     * Backbone View for CKEditor toolbar configuration; visual UX.
+     *
+     * @constructs
+     *
+     * @augments Backbone.View
+     */
+    initialize: function () {
+      this.listenTo(this.model, 'change:isDirty change:groupNamesVisible', this.render);
+
+      // Add a toggle for the button group names.
+      $(Drupal.theme('ckeditorButtonGroupNamesToggle'))
+        .prependTo(this.$el.find('#ckeditor-active-toolbar').parent());
+
+      this.render();
+    },
+
+    /**
+     * Render function for rendering the toolbar configuration.
+     *
+     * @param {*} model
+     *   Model used for the view.
+     * @param {string} [value]
+     *   The value that was changed.
+     * @param {object} changedAttributes
+     *   The attributes that was changed.
+     *
+     * @return {Drupal.ckeditor.VisualView}
+     *   The {@link Drupal.ckeditor.VisualView} object.
+     */
+    render: function (model, value, changedAttributes) {
+      this.insertPlaceholders();
+      this.applySorting();
+
+      // Toggle button group names.
+      var groupNamesVisible = this.model.get('groupNamesVisible');
+      // If a button was just placed in the active toolbar, ensure that the
+      // button group names are visible.
+      if (changedAttributes && changedAttributes.changes && changedAttributes.changes.isDirty) {
+        this.model.set({groupNamesVisible: true}, {silent: true});
+        groupNamesVisible = true;
+      }
+      this.$el.find('[data-toolbar="active"]').toggleClass('ckeditor-group-names-are-visible', groupNamesVisible);
+      this.$el.find('.ckeditor-groupnames-toggle')
+        .text((groupNamesVisible) ? Drupal.t('Hide group names') : Drupal.t('Show group names'))
+        .attr('aria-pressed', groupNamesVisible);
+
+      return this;
+    },
+
+    /**
+     * Handles clicks to a button group name.
+     *
+     * @param {jQuery.Event} event
+     *   The click event on the button group.
+     */
+    onGroupNameClick: function (event) {
+      var $group = $(event.currentTarget).closest('.ckeditor-toolbar-group');
+      Drupal.ckeditor.openGroupNameDialog(this, $group);
+
+      event.stopPropagation();
+      event.preventDefault();
+    },
+
+    /**
+     * Handles clicks on the button group names toggle button.
+     *
+     * @param {jQuery.Event} event
+     *   The click event on the toggle button.
+     */
+    onGroupNamesToggleClick: function (event) {
+      this.model.set('groupNamesVisible', !this.model.get('groupNamesVisible'));
+      event.preventDefault();
+    },
+
+    /**
+     * Prompts the user to provide a name for a new button group; inserts it.
+     *
+     * @param {jQuery.Event} event
+     *   The event of the button click.
+     */
+    onAddGroupButtonClick: function (event) {
+
+      /**
+       * Inserts a new button if the openGroupNameDialog function returns true.
+       *
+       * @param {bool} success
+       *   A flag that indicates if the user created a new group (true) or
+       *   canceled out of the dialog (false).
+       * @param {jQuery} $group
+       *   A jQuery DOM fragment that represents the new button group. It has
+       *   not been added to the DOM yet.
+       */
+      function insertNewGroup(success, $group) {
+        if (success) {
+          $group.appendTo($(event.currentTarget).closest('.ckeditor-row').children('.ckeditor-toolbar-groups'));
+          // Focus on the new group.
+          $group.trigger('focus');
+        }
+      }
+
+      // Pass in a DOM fragment of a placeholder group so that the new group
+      // name can be applied to it.
+      Drupal.ckeditor.openGroupNameDialog(this, $(Drupal.theme('ckeditorToolbarGroup')), insertNewGroup);
+
+      event.preventDefault();
+    },
+
+    /**
+     * Handles jQuery Sortable stop sort of a button group.
+     *
+     * @param {jQuery.Event} event
+     *   The event triggered on the group drag.
+     * @param {object} ui
+     *   A jQuery.ui.sortable argument that contains information about the
+     *   elements involved in the sort action.
+     */
+    endGroupDrag: function (event, ui) {
+      var view = this;
+      Drupal.ckeditor.registerGroupMove(this, ui.item, function (success) {
+        if (!success) {
+          // Cancel any sorting in the configuration area.
+          view.$el.find('.ckeditor-toolbar-configuration').find('.ui-sortable').sortable('cancel');
+        }
+      });
+    },
+
+    /**
+     * Handles jQuery Sortable start sort of a button.
+     *
+     * @param {jQuery.Event} event
+     *   The event triggered on the group drag.
+     * @param {object} ui
+     *   A jQuery.ui.sortable argument that contains information about the
+     *   elements involved in the sort action.
+     */
+    startButtonDrag: function (event, ui) {
+      this.$el.find('a:focus').trigger('blur');
+
+      // Show the button group names as soon as the user starts dragging.
+      this.model.set('groupNamesVisible', true);
+    },
+
+    /**
+     * Handles jQuery Sortable stop sort of a button.
+     *
+     * @param {jQuery.Event} event
+     *   The event triggered on the button drag.
+     * @param {object} ui
+     *   A jQuery.ui.sortable argument that contains information about the
+     *   elements involved in the sort action.
+     */
+    endButtonDrag: function (event, ui) {
+      var view = this;
+      Drupal.ckeditor.registerButtonMove(this, ui.item, function (success) {
+        if (!success) {
+          // Cancel any sorting in the configuration area.
+          view.$el.find('.ui-sortable').sortable('cancel');
+        }
+        // Refocus the target button so that the user can continue from a known
+        // place.
+        ui.item.find('a').trigger('focus');
+      });
+    },
+
+    /**
+     * Invokes jQuery.sortable() on new buttons and groups in a CKEditor config.
+     */
+    applySorting: function () {
+      // Make the buttons sortable.
+      this.$el.find('.ckeditor-buttons').not('.ui-sortable').sortable({
+        // Change this to .ckeditor-toolbar-group-buttons.
+        connectWith: '.ckeditor-buttons',
+        placeholder: 'ckeditor-button-placeholder',
+        forcePlaceholderSize: true,
+        tolerance: 'pointer',
+        cursor: 'move',
+        start: this.startButtonDrag.bind(this),
+        // Sorting within a sortable.
+        stop: this.endButtonDrag.bind(this)
+      }).disableSelection();
+
+      // Add the drag and drop functionality to button groups.
+      this.$el.find('.ckeditor-toolbar-groups').not('.ui-sortable').sortable({
+        connectWith: '.ckeditor-toolbar-groups',
+        cancel: '.ckeditor-add-new-group',
+        placeholder: 'ckeditor-toolbar-group-placeholder',
+        forcePlaceholderSize: true,
+        cursor: 'move',
+        stop: this.endGroupDrag.bind(this)
+      });
+
+      // Add the drag and drop functionality to buttons.
+      this.$el.find('.ckeditor-multiple-buttons li').draggable({
+        connectToSortable: '.ckeditor-toolbar-active .ckeditor-buttons',
+        helper: 'clone'
+      });
+    },
+
+    /**
+     * Wraps the invocation of methods to insert blank groups and rows.
+     */
+    insertPlaceholders: function () {
+      this.insertPlaceholderRow();
+      this.insertNewGroupButtons();
+    },
+
+    /**
+     * Inserts a blank row at the bottom of the CKEditor configuration.
+     */
+    insertPlaceholderRow: function () {
+      var $rows = this.$el.find('.ckeditor-row');
+      // Add a placeholder row. to the end of the list if one does not exist.
+      if (!$rows.eq(-1).hasClass('placeholder')) {
+        this.$el
+          .find('.ckeditor-toolbar-active')
+          .children('.ckeditor-active-toolbar-configuration')
+          .append(Drupal.theme('ckeditorRow'));
+      }
+      // Update the $rows variable to include the new row.
+      $rows = this.$el.find('.ckeditor-row');
+      // Remove blank rows except the last one.
+      var len = $rows.length;
+      $rows.filter(function (index, row) {
+        // Do not remove the last row.
+        if (index + 1 === len) {
+          return false;
+        }
+        return $(row).find('.ckeditor-toolbar-group').not('.placeholder').length === 0;
+      })
+        // Then get all rows that are placeholders and remove them.
+        .remove();
+    },
+
+    /**
+     * Inserts a button in each row that will add a new CKEditor button group.
+     */
+    insertNewGroupButtons: function () {
+      // Insert an add group button to each row.
+      this.$el.find('.ckeditor-row').each(function () {
+        var $row = $(this);
+        var $groups = $row.find('.ckeditor-toolbar-group');
+        var $button = $row.find('.ckeditor-add-new-group');
+        if ($button.length === 0) {
+          $row.children('.ckeditor-toolbar-groups').append(Drupal.theme('ckeditorNewButtonGroup'));
+        }
+        // If a placeholder group exists, make sure it's at the end of the row.
+        else if (!$groups.eq(-1).hasClass('ckeditor-add-new-group')) {
+          $button.appendTo($row.children('.ckeditor-toolbar-groups'));
+        }
+      });
+    }
+  });
+
+})(Drupal, Backbone, jQuery);
diff --git a/core/modules/ckeditor/js/views/VisualView.js b/core/modules/ckeditor/js/views/VisualView.js
index 2d8042ebbf68..67368d8901a2 100644
--- a/core/modules/ckeditor/js/views/VisualView.js
+++ b/core/modules/ckeditor/js/views/VisualView.js
@@ -1,14 +1,16 @@
 /**
- * @file
- * A Backbone View that provides the visual UX view of CKEditor toolbar
- *   configuration.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/ckeditor/js/views/VisualView.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function (Drupal, Backbone, $) {
 
   'use strict';
 
-  Drupal.ckeditor.VisualView = Backbone.View.extend(/** @lends Drupal.ckeditor.VisualView# */{
+  Drupal.ckeditor.VisualView = Backbone.View.extend({
 
     events: {
       'click .ckeditor-toolbar-group-name': 'onGroupNameClick',
@@ -16,63 +18,31 @@
       'click .ckeditor-add-new-group button': 'onAddGroupButtonClick'
     },
 
-    /**
-     * Backbone View for CKEditor toolbar configuration; visual UX.
-     *
-     * @constructs
-     *
-     * @augments Backbone.View
-     */
-    initialize: function () {
+    initialize: function initialize() {
       this.listenTo(this.model, 'change:isDirty change:groupNamesVisible', this.render);
 
-      // Add a toggle for the button group names.
-      $(Drupal.theme('ckeditorButtonGroupNamesToggle'))
-        .prependTo(this.$el.find('#ckeditor-active-toolbar').parent());
+      $(Drupal.theme('ckeditorButtonGroupNamesToggle')).prependTo(this.$el.find('#ckeditor-active-toolbar').parent());
 
       this.render();
     },
 
-    /**
-     * Render function for rendering the toolbar configuration.
-     *
-     * @param {*} model
-     *   Model used for the view.
-     * @param {string} [value]
-     *   The value that was changed.
-     * @param {object} changedAttributes
-     *   The attributes that was changed.
-     *
-     * @return {Drupal.ckeditor.VisualView}
-     *   The {@link Drupal.ckeditor.VisualView} object.
-     */
-    render: function (model, value, changedAttributes) {
+    render: function render(model, value, changedAttributes) {
       this.insertPlaceholders();
       this.applySorting();
 
-      // Toggle button group names.
       var groupNamesVisible = this.model.get('groupNamesVisible');
-      // If a button was just placed in the active toolbar, ensure that the
-      // button group names are visible.
+
       if (changedAttributes && changedAttributes.changes && changedAttributes.changes.isDirty) {
-        this.model.set({groupNamesVisible: true}, {silent: true});
+        this.model.set({ groupNamesVisible: true }, { silent: true });
         groupNamesVisible = true;
       }
       this.$el.find('[data-toolbar="active"]').toggleClass('ckeditor-group-names-are-visible', groupNamesVisible);
-      this.$el.find('.ckeditor-groupnames-toggle')
-        .text((groupNamesVisible) ? Drupal.t('Hide group names') : Drupal.t('Show group names'))
-        .attr('aria-pressed', groupNamesVisible);
+      this.$el.find('.ckeditor-groupnames-toggle').text(groupNamesVisible ? Drupal.t('Hide group names') : Drupal.t('Show group names')).attr('aria-pressed', groupNamesVisible);
 
       return this;
     },
 
-    /**
-     * Handles clicks to a button group name.
-     *
-     * @param {jQuery.Event} event
-     *   The click event on the button group.
-     */
-    onGroupNameClick: function (event) {
+    onGroupNameClick: function onGroupNameClick(event) {
       var $group = $(event.currentTarget).closest('.ckeditor-toolbar-group');
       Drupal.ckeditor.openGroupNameDialog(this, $group);
 
@@ -80,125 +50,63 @@
       event.preventDefault();
     },
 
-    /**
-     * Handles clicks on the button group names toggle button.
-     *
-     * @param {jQuery.Event} event
-     *   The click event on the toggle button.
-     */
-    onGroupNamesToggleClick: function (event) {
+    onGroupNamesToggleClick: function onGroupNamesToggleClick(event) {
       this.model.set('groupNamesVisible', !this.model.get('groupNamesVisible'));
       event.preventDefault();
     },
 
-    /**
-     * Prompts the user to provide a name for a new button group; inserts it.
-     *
-     * @param {jQuery.Event} event
-     *   The event of the button click.
-     */
-    onAddGroupButtonClick: function (event) {
-
-      /**
-       * Inserts a new button if the openGroupNameDialog function returns true.
-       *
-       * @param {bool} success
-       *   A flag that indicates if the user created a new group (true) or
-       *   canceled out of the dialog (false).
-       * @param {jQuery} $group
-       *   A jQuery DOM fragment that represents the new button group. It has
-       *   not been added to the DOM yet.
-       */
+    onAddGroupButtonClick: function onAddGroupButtonClick(event) {
       function insertNewGroup(success, $group) {
         if (success) {
           $group.appendTo($(event.currentTarget).closest('.ckeditor-row').children('.ckeditor-toolbar-groups'));
-          // Focus on the new group.
+
           $group.trigger('focus');
         }
       }
 
-      // Pass in a DOM fragment of a placeholder group so that the new group
-      // name can be applied to it.
       Drupal.ckeditor.openGroupNameDialog(this, $(Drupal.theme('ckeditorToolbarGroup')), insertNewGroup);
 
       event.preventDefault();
     },
 
-    /**
-     * Handles jQuery Sortable stop sort of a button group.
-     *
-     * @param {jQuery.Event} event
-     *   The event triggered on the group drag.
-     * @param {object} ui
-     *   A jQuery.ui.sortable argument that contains information about the
-     *   elements involved in the sort action.
-     */
-    endGroupDrag: function (event, ui) {
+    endGroupDrag: function endGroupDrag(event, ui) {
       var view = this;
       Drupal.ckeditor.registerGroupMove(this, ui.item, function (success) {
         if (!success) {
-          // Cancel any sorting in the configuration area.
           view.$el.find('.ckeditor-toolbar-configuration').find('.ui-sortable').sortable('cancel');
         }
       });
     },
 
-    /**
-     * Handles jQuery Sortable start sort of a button.
-     *
-     * @param {jQuery.Event} event
-     *   The event triggered on the group drag.
-     * @param {object} ui
-     *   A jQuery.ui.sortable argument that contains information about the
-     *   elements involved in the sort action.
-     */
-    startButtonDrag: function (event, ui) {
+    startButtonDrag: function startButtonDrag(event, ui) {
       this.$el.find('a:focus').trigger('blur');
 
-      // Show the button group names as soon as the user starts dragging.
       this.model.set('groupNamesVisible', true);
     },
 
-    /**
-     * Handles jQuery Sortable stop sort of a button.
-     *
-     * @param {jQuery.Event} event
-     *   The event triggered on the button drag.
-     * @param {object} ui
-     *   A jQuery.ui.sortable argument that contains information about the
-     *   elements involved in the sort action.
-     */
-    endButtonDrag: function (event, ui) {
+    endButtonDrag: function endButtonDrag(event, ui) {
       var view = this;
       Drupal.ckeditor.registerButtonMove(this, ui.item, function (success) {
         if (!success) {
-          // Cancel any sorting in the configuration area.
           view.$el.find('.ui-sortable').sortable('cancel');
         }
-        // Refocus the target button so that the user can continue from a known
-        // place.
+
         ui.item.find('a').trigger('focus');
       });
     },
 
-    /**
-     * Invokes jQuery.sortable() on new buttons and groups in a CKEditor config.
-     */
-    applySorting: function () {
-      // Make the buttons sortable.
+    applySorting: function applySorting() {
       this.$el.find('.ckeditor-buttons').not('.ui-sortable').sortable({
-        // Change this to .ckeditor-toolbar-group-buttons.
         connectWith: '.ckeditor-buttons',
         placeholder: 'ckeditor-button-placeholder',
         forcePlaceholderSize: true,
         tolerance: 'pointer',
         cursor: 'move',
         start: this.startButtonDrag.bind(this),
-        // Sorting within a sortable.
+
         stop: this.endButtonDrag.bind(this)
       }).disableSelection();
 
-      // Add the drag and drop functionality to button groups.
       this.$el.find('.ckeditor-toolbar-groups').not('.ui-sortable').sortable({
         connectWith: '.ckeditor-toolbar-groups',
         cancel: '.ckeditor-add-new-group',
@@ -208,66 +116,46 @@
         stop: this.endGroupDrag.bind(this)
       });
 
-      // Add the drag and drop functionality to buttons.
       this.$el.find('.ckeditor-multiple-buttons li').draggable({
         connectToSortable: '.ckeditor-toolbar-active .ckeditor-buttons',
         helper: 'clone'
       });
     },
 
-    /**
-     * Wraps the invocation of methods to insert blank groups and rows.
-     */
-    insertPlaceholders: function () {
+    insertPlaceholders: function insertPlaceholders() {
       this.insertPlaceholderRow();
       this.insertNewGroupButtons();
     },
 
-    /**
-     * Inserts a blank row at the bottom of the CKEditor configuration.
-     */
-    insertPlaceholderRow: function () {
+    insertPlaceholderRow: function insertPlaceholderRow() {
       var $rows = this.$el.find('.ckeditor-row');
-      // Add a placeholder row. to the end of the list if one does not exist.
+
       if (!$rows.eq(-1).hasClass('placeholder')) {
-        this.$el
-          .find('.ckeditor-toolbar-active')
-          .children('.ckeditor-active-toolbar-configuration')
-          .append(Drupal.theme('ckeditorRow'));
+        this.$el.find('.ckeditor-toolbar-active').children('.ckeditor-active-toolbar-configuration').append(Drupal.theme('ckeditorRow'));
       }
-      // Update the $rows variable to include the new row.
+
       $rows = this.$el.find('.ckeditor-row');
-      // Remove blank rows except the last one.
+
       var len = $rows.length;
       $rows.filter(function (index, row) {
-        // Do not remove the last row.
         if (index + 1 === len) {
           return false;
         }
         return $(row).find('.ckeditor-toolbar-group').not('.placeholder').length === 0;
-      })
-        // Then get all rows that are placeholders and remove them.
-        .remove();
+      }).remove();
     },
 
-    /**
-     * Inserts a button in each row that will add a new CKEditor button group.
-     */
-    insertNewGroupButtons: function () {
-      // Insert an add group button to each row.
+    insertNewGroupButtons: function insertNewGroupButtons() {
       this.$el.find('.ckeditor-row').each(function () {
         var $row = $(this);
         var $groups = $row.find('.ckeditor-toolbar-group');
         var $button = $row.find('.ckeditor-add-new-group');
         if ($button.length === 0) {
           $row.children('.ckeditor-toolbar-groups').append(Drupal.theme('ckeditorNewButtonGroup'));
-        }
-        // If a placeholder group exists, make sure it's at the end of the row.
-        else if (!$groups.eq(-1).hasClass('ckeditor-add-new-group')) {
-          $button.appendTo($row.children('.ckeditor-toolbar-groups'));
-        }
+        } else if (!$groups.eq(-1).hasClass('ckeditor-add-new-group')) {
+            $button.appendTo($row.children('.ckeditor-toolbar-groups'));
+          }
       });
     }
   });
-
-})(Drupal, Backbone, jQuery);
+})(Drupal, Backbone, jQuery);
\ No newline at end of file
diff --git a/core/modules/ckeditor/tests/modules/js/ajax-css.es6.js b/core/modules/ckeditor/tests/modules/js/ajax-css.es6.js
new file mode 100644
index 000000000000..142b4eea0f4d
--- /dev/null
+++ b/core/modules/ckeditor/tests/modules/js/ajax-css.es6.js
@@ -0,0 +1,24 @@
+/**
+ * @file
+ * Contains client-side code for testing CSS delivered to CKEditor via AJAX.
+ */
+
+(function (Drupal, ckeditor, editorSettings, $) {
+
+  'use strict';
+
+  Drupal.behaviors.ajaxCssForm = {
+
+    attach: function (context) {
+      // Initialize an inline CKEditor on the #edit-inline element if it
+      // isn't editable already.
+      $(context)
+        .find('#edit-inline')
+        .not('[contenteditable]')
+        .each(function () {
+          ckeditor.attachInlineEditor(this, editorSettings.formats.test_format);
+        });
+    }
+  };
+
+})(Drupal, Drupal.editors.ckeditor, drupalSettings.editor, jQuery);
diff --git a/core/modules/ckeditor/tests/modules/js/ajax-css.js b/core/modules/ckeditor/tests/modules/js/ajax-css.js
index 142b4eea0f4d..ca78134f5114 100644
--- a/core/modules/ckeditor/tests/modules/js/ajax-css.js
+++ b/core/modules/ckeditor/tests/modules/js/ajax-css.js
@@ -1,7 +1,10 @@
 /**
- * @file
- * Contains client-side code for testing CSS delivered to CKEditor via AJAX.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/ckeditor/tests/modules/js/ajax-css.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function (Drupal, ckeditor, editorSettings, $) {
 
@@ -9,16 +12,10 @@
 
   Drupal.behaviors.ajaxCssForm = {
 
-    attach: function (context) {
-      // Initialize an inline CKEditor on the #edit-inline element if it
-      // isn't editable already.
-      $(context)
-        .find('#edit-inline')
-        .not('[contenteditable]')
-        .each(function () {
-          ckeditor.attachInlineEditor(this, editorSettings.formats.test_format);
-        });
+    attach: function attach(context) {
+      $(context).find('#edit-inline').not('[contenteditable]').each(function () {
+        ckeditor.attachInlineEditor(this, editorSettings.formats.test_format);
+      });
     }
   };
-
-})(Drupal, Drupal.editors.ckeditor, drupalSettings.editor, jQuery);
+})(Drupal, Drupal.editors.ckeditor, drupalSettings.editor, jQuery);
\ No newline at end of file
diff --git a/core/modules/color/color.es6.js b/core/modules/color/color.es6.js
new file mode 100644
index 000000000000..984f740126d4
--- /dev/null
+++ b/core/modules/color/color.es6.js
@@ -0,0 +1,297 @@
+/**
+ * @file
+ * Attaches the behaviors for the Color module.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * Displays farbtastic color selector and initialize color administration UI.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attach color selection behavior to relevant context.
+   */
+  Drupal.behaviors.color = {
+    attach: function (context, settings) {
+      var i;
+      var j;
+      var colors;
+      // This behavior attaches by ID, so is only valid once on a page.
+      var form = $(context).find('#system-theme-settings .color-form').once('color');
+      if (form.length === 0) {
+        return;
+      }
+      var inputs = [];
+      var hooks = [];
+      var locks = [];
+      var focused = null;
+
+      // Add Farbtastic.
+      $('<div class="color-placeholder"></div>').once('color').prependTo(form);
+      var farb = $.farbtastic('.color-placeholder');
+
+      // Decode reference colors to HSL.
+      var reference = settings.color.reference;
+      for (i in reference) {
+        if (reference.hasOwnProperty(i)) {
+          reference[i] = farb.RGBToHSL(farb.unpack(reference[i]));
+        }
+      }
+
+      // Build a preview.
+      var height = [];
+      var width = [];
+      // Loop through all defined gradients.
+      for (i in settings.gradients) {
+        if (settings.gradients.hasOwnProperty(i)) {
+          // Add element to display the gradient.
+          $('.color-preview').once('color').append('<div id="gradient-' + i + '"></div>');
+          var gradient = $('.color-preview #gradient-' + i);
+          // Add height of current gradient to the list (divided by 10).
+          height.push(parseInt(gradient.css('height'), 10) / 10);
+          // Add width of current gradient to the list (divided by 10).
+          width.push(parseInt(gradient.css('width'), 10) / 10);
+          // Add rows (or columns for horizontal gradients).
+          // Each gradient line should have a height (or width for horizontal
+          // gradients) of 10px (because we divided the height/width by 10
+          // above).
+          for (j = 0; j < (settings.gradients[i].direction === 'vertical' ? height[i] : width[i]); ++j) {
+            gradient.append('<div class="gradient-line"></div>');
+          }
+        }
+      }
+
+      // Set up colorScheme selector.
+      form.find('#edit-scheme').on('change', function () {
+        var schemes = settings.color.schemes;
+        var colorScheme = this.options[this.selectedIndex].value;
+        if (colorScheme !== '' && schemes[colorScheme]) {
+          // Get colors of active scheme.
+          colors = schemes[colorScheme];
+          for (var fieldName in colors) {
+            if (colors.hasOwnProperty(fieldName)) {
+              callback($('#edit-palette-' + fieldName), colors[fieldName], false, true);
+            }
+          }
+          preview();
+        }
+      });
+
+      /**
+       * Renders the preview.
+       */
+      function preview() {
+        Drupal.color.callback(context, settings, form, farb, height, width);
+      }
+
+      /**
+       * Shifts a given color, using a reference pair (ref in HSL).
+       *
+       * This algorithm ensures relative ordering on the saturation and
+       * luminance axes is preserved, and performs a simple hue shift.
+       *
+       * It is also symmetrical. If: shift_color(c, a, b) === d, then
+       * shift_color(d, b, a) === c.
+       *
+       * @function Drupal.color~shift_color
+       *
+       * @param {string} given
+       *   A hex color code to shift.
+       * @param {Array.<number>} ref1
+       *   First HSL color reference.
+       * @param {Array.<number>} ref2
+       *   Second HSL color reference.
+       *
+       * @return {string}
+       *   A hex color, shifted.
+       */
+      function shift_color(given, ref1, ref2) {
+        var d;
+        // Convert to HSL.
+        given = farb.RGBToHSL(farb.unpack(given));
+
+        // Hue: apply delta.
+        given[0] += ref2[0] - ref1[0];
+
+        // Saturation: interpolate.
+        if (ref1[1] === 0 || ref2[1] === 0) {
+          given[1] = ref2[1];
+        }
+        else {
+          d = ref1[1] / ref2[1];
+          if (d > 1) {
+            given[1] /= d;
+          }
+          else {
+            given[1] = 1 - (1 - given[1]) * d;
+          }
+        }
+
+        // Luminance: interpolate.
+        if (ref1[2] === 0 || ref2[2] === 0) {
+          given[2] = ref2[2];
+        }
+        else {
+          d = ref1[2] / ref2[2];
+          if (d > 1) {
+            given[2] /= d;
+          }
+          else {
+            given[2] = 1 - (1 - given[2]) * d;
+          }
+        }
+
+        return farb.pack(farb.HSLToRGB(given));
+      }
+
+      /**
+       * Callback for Farbtastic when a new color is chosen.
+       *
+       * @param {HTMLElement} input
+       *   The input element where the color is chosen.
+       * @param {string} color
+       *   The color that was chosen through the input.
+       * @param {bool} propagate
+       *   Whether or not to propagate the color to a locked pair value
+       * @param {bool} colorScheme
+       *   Flag to indicate if the user is using a color scheme when changing
+       *   the color.
+       */
+      function callback(input, color, propagate, colorScheme) {
+        var matched;
+        // Set background/foreground colors.
+        $(input).css({
+          backgroundColor: color,
+          color: farb.RGBToHSL(farb.unpack(color))[2] > 0.5 ? '#000' : '#fff'
+        });
+
+        // Change input value.
+        if ($(input).val() && $(input).val() !== color) {
+          $(input).val(color);
+
+          // Update locked values.
+          if (propagate) {
+            i = input.i;
+            for (j = i + 1; ; ++j) {
+              if (!locks[j - 1] || $(locks[j - 1]).is('.is-unlocked')) {
+                break;
+              }
+              matched = shift_color(color, reference[input.key], reference[inputs[j].key]);
+              callback(inputs[j], matched, false);
+            }
+            for (j = i - 1; ; --j) {
+              if (!locks[j] || $(locks[j]).is('.is-unlocked')) {
+                break;
+              }
+              matched = shift_color(color, reference[input.key], reference[inputs[j].key]);
+              callback(inputs[j], matched, false);
+            }
+
+            // Update preview.
+            preview();
+          }
+
+          // Reset colorScheme selector.
+          if (!colorScheme) {
+            resetScheme();
+          }
+        }
+      }
+
+      /**
+       * Resets the color scheme selector.
+       */
+      function resetScheme() {
+        form.find('#edit-scheme').each(function () {
+          this.selectedIndex = this.options.length - 1;
+        });
+      }
+
+      /**
+       * Focuses Farbtastic on a particular field.
+       *
+       * @param {jQuery.Event} e
+       *   The focus event on the field.
+       */
+      function focus(e) {
+        var input = e.target;
+        // Remove old bindings.
+        if (focused) {
+          $(focused).off('keyup', farb.updateValue)
+            .off('keyup', preview).off('keyup', resetScheme)
+            .parent().removeClass('item-selected');
+        }
+
+        // Add new bindings.
+        focused = input;
+        farb.linkTo(function (color) { callback(input, color, true, false); });
+        farb.setColor(input.value);
+        $(focused).on('keyup', farb.updateValue).on('keyup', preview).on('keyup', resetScheme)
+          .parent().addClass('item-selected');
+      }
+
+      // Initialize color fields.
+      form.find('.js-color-palette input.form-text')
+        .each(function () {
+          // Extract palette field name.
+          this.key = this.id.substring(13);
+
+          // Link to color picker temporarily to initialize.
+          farb.linkTo(function () {}).setColor('#000').linkTo(this);
+
+          // Add lock.
+          var i = inputs.length;
+          if (inputs.length) {
+            var toggleClick = true;
+            var lock = $('<button class="color-palette__lock">' + Drupal.t('Unlock') + '</button>').on('click', function (e) {
+              e.preventDefault();
+              if (toggleClick) {
+                $(this).addClass('is-unlocked').html(Drupal.t('Lock'));
+                $(hooks[i - 1]).attr('class',
+                  locks[i - 2] && $(locks[i - 2]).is(':not(.is-unlocked)') ? 'color-palette__hook is-up' : 'color-palette__hook'
+                );
+                $(hooks[i]).attr('class',
+                  locks[i] && $(locks[i]).is(':not(.is-unlocked)') ? 'color-palette__hook is-down' : 'color-palette__hook'
+                );
+              }
+              else {
+                $(this).removeClass('is-unlocked').html(Drupal.t('Unlock'));
+                $(hooks[i - 1]).attr('class',
+                  locks[i - 2] && $(locks[i - 2]).is(':not(.is-unlocked)') ? 'color-palette__hook is-both' : 'color-palette__hook is-down'
+                );
+                $(hooks[i]).attr('class',
+                  locks[i] && $(locks[i]).is(':not(.is-unlocked)') ? 'color-palette__hook is-both' : 'color-palette__hook is-up'
+                );
+              }
+              toggleClick = !toggleClick;
+            });
+            $(this).after(lock);
+            locks.push(lock);
+          }
+
+          // Add hook.
+          var hook = $('<div class="color-palette__hook"></div>');
+          $(this).after(hook);
+          hooks.push(hook);
+
+          $(this).parent().find('.color-palette__lock').trigger('click');
+          this.i = i;
+          inputs.push(this);
+        })
+        .on('focus', focus);
+
+      form.find('.js-color-palette label');
+
+      // Focus first color.
+      inputs[0].focus();
+
+      // Render preview.
+      preview();
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/core/modules/color/color.js b/core/modules/color/color.js
index 984f740126d4..78d3ba5f6937 100644
--- a/core/modules/color/color.js
+++ b/core/modules/color/color.js
@@ -1,26 +1,21 @@
 /**
- * @file
- * Attaches the behaviors for the Color module.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/color/color.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   * Displays farbtastic color selector and initialize color administration UI.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attach color selection behavior to relevant context.
-   */
   Drupal.behaviors.color = {
-    attach: function (context, settings) {
+    attach: function attach(context, settings) {
       var i;
       var j;
       var colors;
-      // This behavior attaches by ID, so is only valid once on a page.
+
       var form = $(context).find('#system-theme-settings .color-form').once('color');
       if (form.length === 0) {
         return;
@@ -30,11 +25,9 @@
       var locks = [];
       var focused = null;
 
-      // Add Farbtastic.
       $('<div class="color-placeholder"></div>').once('color').prependTo(form);
       var farb = $.farbtastic('.color-placeholder');
 
-      // Decode reference colors to HSL.
       var reference = settings.color.reference;
       for (i in reference) {
         if (reference.hasOwnProperty(i)) {
@@ -42,35 +35,28 @@
         }
       }
 
-      // Build a preview.
       var height = [];
       var width = [];
-      // Loop through all defined gradients.
+
       for (i in settings.gradients) {
         if (settings.gradients.hasOwnProperty(i)) {
-          // Add element to display the gradient.
           $('.color-preview').once('color').append('<div id="gradient-' + i + '"></div>');
           var gradient = $('.color-preview #gradient-' + i);
-          // Add height of current gradient to the list (divided by 10).
+
           height.push(parseInt(gradient.css('height'), 10) / 10);
-          // Add width of current gradient to the list (divided by 10).
+
           width.push(parseInt(gradient.css('width'), 10) / 10);
-          // Add rows (or columns for horizontal gradients).
-          // Each gradient line should have a height (or width for horizontal
-          // gradients) of 10px (because we divided the height/width by 10
-          // above).
+
           for (j = 0; j < (settings.gradients[i].direction === 'vertical' ? height[i] : width[i]); ++j) {
             gradient.append('<div class="gradient-line"></div>');
           }
         }
       }
 
-      // Set up colorScheme selector.
       form.find('#edit-scheme').on('change', function () {
         var schemes = settings.color.schemes;
         var colorScheme = this.options[this.selectedIndex].value;
         if (colorScheme !== '' && schemes[colorScheme]) {
-          // Get colors of active scheme.
           colors = schemes[colorScheme];
           for (var fieldName in colors) {
             if (colors.hasOwnProperty(fieldName)) {
@@ -81,66 +67,35 @@
         }
       });
 
-      /**
-       * Renders the preview.
-       */
       function preview() {
         Drupal.color.callback(context, settings, form, farb, height, width);
       }
 
-      /**
-       * Shifts a given color, using a reference pair (ref in HSL).
-       *
-       * This algorithm ensures relative ordering on the saturation and
-       * luminance axes is preserved, and performs a simple hue shift.
-       *
-       * It is also symmetrical. If: shift_color(c, a, b) === d, then
-       * shift_color(d, b, a) === c.
-       *
-       * @function Drupal.color~shift_color
-       *
-       * @param {string} given
-       *   A hex color code to shift.
-       * @param {Array.<number>} ref1
-       *   First HSL color reference.
-       * @param {Array.<number>} ref2
-       *   Second HSL color reference.
-       *
-       * @return {string}
-       *   A hex color, shifted.
-       */
       function shift_color(given, ref1, ref2) {
         var d;
-        // Convert to HSL.
+
         given = farb.RGBToHSL(farb.unpack(given));
 
-        // Hue: apply delta.
         given[0] += ref2[0] - ref1[0];
 
-        // Saturation: interpolate.
         if (ref1[1] === 0 || ref2[1] === 0) {
           given[1] = ref2[1];
-        }
-        else {
+        } else {
           d = ref1[1] / ref2[1];
           if (d > 1) {
             given[1] /= d;
-          }
-          else {
+          } else {
             given[1] = 1 - (1 - given[1]) * d;
           }
         }
 
-        // Luminance: interpolate.
         if (ref1[2] === 0 || ref2[2] === 0) {
           given[2] = ref2[2];
-        }
-        else {
+        } else {
           d = ref1[2] / ref2[2];
           if (d > 1) {
             given[2] /= d;
-          }
-          else {
+          } else {
             given[2] = 1 - (1 - given[2]) * d;
           }
         }
@@ -148,42 +103,27 @@
         return farb.pack(farb.HSLToRGB(given));
       }
 
-      /**
-       * Callback for Farbtastic when a new color is chosen.
-       *
-       * @param {HTMLElement} input
-       *   The input element where the color is chosen.
-       * @param {string} color
-       *   The color that was chosen through the input.
-       * @param {bool} propagate
-       *   Whether or not to propagate the color to a locked pair value
-       * @param {bool} colorScheme
-       *   Flag to indicate if the user is using a color scheme when changing
-       *   the color.
-       */
       function callback(input, color, propagate, colorScheme) {
         var matched;
-        // Set background/foreground colors.
+
         $(input).css({
           backgroundColor: color,
           color: farb.RGBToHSL(farb.unpack(color))[2] > 0.5 ? '#000' : '#fff'
         });
 
-        // Change input value.
         if ($(input).val() && $(input).val() !== color) {
           $(input).val(color);
 
-          // Update locked values.
           if (propagate) {
             i = input.i;
-            for (j = i + 1; ; ++j) {
+            for (j = i + 1;; ++j) {
               if (!locks[j - 1] || $(locks[j - 1]).is('.is-unlocked')) {
                 break;
               }
               matched = shift_color(color, reference[input.key], reference[inputs[j].key]);
               callback(inputs[j], matched, false);
             }
-            for (j = i - 1; ; --j) {
+            for (j = i - 1;; --j) {
               if (!locks[j] || $(locks[j]).is('.is-unlocked')) {
                 break;
               }
@@ -191,107 +131,75 @@
               callback(inputs[j], matched, false);
             }
 
-            // Update preview.
             preview();
           }
 
-          // Reset colorScheme selector.
           if (!colorScheme) {
             resetScheme();
           }
         }
       }
 
-      /**
-       * Resets the color scheme selector.
-       */
       function resetScheme() {
         form.find('#edit-scheme').each(function () {
           this.selectedIndex = this.options.length - 1;
         });
       }
 
-      /**
-       * Focuses Farbtastic on a particular field.
-       *
-       * @param {jQuery.Event} e
-       *   The focus event on the field.
-       */
       function focus(e) {
         var input = e.target;
-        // Remove old bindings.
+
         if (focused) {
-          $(focused).off('keyup', farb.updateValue)
-            .off('keyup', preview).off('keyup', resetScheme)
-            .parent().removeClass('item-selected');
+          $(focused).off('keyup', farb.updateValue).off('keyup', preview).off('keyup', resetScheme).parent().removeClass('item-selected');
         }
 
-        // Add new bindings.
         focused = input;
-        farb.linkTo(function (color) { callback(input, color, true, false); });
+        farb.linkTo(function (color) {
+          callback(input, color, true, false);
+        });
         farb.setColor(input.value);
-        $(focused).on('keyup', farb.updateValue).on('keyup', preview).on('keyup', resetScheme)
-          .parent().addClass('item-selected');
+        $(focused).on('keyup', farb.updateValue).on('keyup', preview).on('keyup', resetScheme).parent().addClass('item-selected');
       }
 
-      // Initialize color fields.
-      form.find('.js-color-palette input.form-text')
-        .each(function () {
-          // Extract palette field name.
-          this.key = this.id.substring(13);
-
-          // Link to color picker temporarily to initialize.
-          farb.linkTo(function () {}).setColor('#000').linkTo(this);
-
-          // Add lock.
-          var i = inputs.length;
-          if (inputs.length) {
-            var toggleClick = true;
-            var lock = $('<button class="color-palette__lock">' + Drupal.t('Unlock') + '</button>').on('click', function (e) {
-              e.preventDefault();
-              if (toggleClick) {
-                $(this).addClass('is-unlocked').html(Drupal.t('Lock'));
-                $(hooks[i - 1]).attr('class',
-                  locks[i - 2] && $(locks[i - 2]).is(':not(.is-unlocked)') ? 'color-palette__hook is-up' : 'color-palette__hook'
-                );
-                $(hooks[i]).attr('class',
-                  locks[i] && $(locks[i]).is(':not(.is-unlocked)') ? 'color-palette__hook is-down' : 'color-palette__hook'
-                );
-              }
-              else {
-                $(this).removeClass('is-unlocked').html(Drupal.t('Unlock'));
-                $(hooks[i - 1]).attr('class',
-                  locks[i - 2] && $(locks[i - 2]).is(':not(.is-unlocked)') ? 'color-palette__hook is-both' : 'color-palette__hook is-down'
-                );
-                $(hooks[i]).attr('class',
-                  locks[i] && $(locks[i]).is(':not(.is-unlocked)') ? 'color-palette__hook is-both' : 'color-palette__hook is-up'
-                );
-              }
-              toggleClick = !toggleClick;
-            });
-            $(this).after(lock);
-            locks.push(lock);
-          }
+      form.find('.js-color-palette input.form-text').each(function () {
+        this.key = this.id.substring(13);
+
+        farb.linkTo(function () {}).setColor('#000').linkTo(this);
+
+        var i = inputs.length;
+        if (inputs.length) {
+          var toggleClick = true;
+          var lock = $('<button class="color-palette__lock">' + Drupal.t('Unlock') + '</button>').on('click', function (e) {
+            e.preventDefault();
+            if (toggleClick) {
+              $(this).addClass('is-unlocked').html(Drupal.t('Lock'));
+              $(hooks[i - 1]).attr('class', locks[i - 2] && $(locks[i - 2]).is(':not(.is-unlocked)') ? 'color-palette__hook is-up' : 'color-palette__hook');
+              $(hooks[i]).attr('class', locks[i] && $(locks[i]).is(':not(.is-unlocked)') ? 'color-palette__hook is-down' : 'color-palette__hook');
+            } else {
+              $(this).removeClass('is-unlocked').html(Drupal.t('Unlock'));
+              $(hooks[i - 1]).attr('class', locks[i - 2] && $(locks[i - 2]).is(':not(.is-unlocked)') ? 'color-palette__hook is-both' : 'color-palette__hook is-down');
+              $(hooks[i]).attr('class', locks[i] && $(locks[i]).is(':not(.is-unlocked)') ? 'color-palette__hook is-both' : 'color-palette__hook is-up');
+            }
+            toggleClick = !toggleClick;
+          });
+          $(this).after(lock);
+          locks.push(lock);
+        }
 
-          // Add hook.
-          var hook = $('<div class="color-palette__hook"></div>');
-          $(this).after(hook);
-          hooks.push(hook);
+        var hook = $('<div class="color-palette__hook"></div>');
+        $(this).after(hook);
+        hooks.push(hook);
 
-          $(this).parent().find('.color-palette__lock').trigger('click');
-          this.i = i;
-          inputs.push(this);
-        })
-        .on('focus', focus);
+        $(this).parent().find('.color-palette__lock').trigger('click');
+        this.i = i;
+        inputs.push(this);
+      }).on('focus', focus);
 
       form.find('.js-color-palette label');
 
-      // Focus first color.
       inputs[0].focus();
 
-      // Render preview.
       preview();
     }
   };
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/modules/color/preview.es6.js b/core/modules/color/preview.es6.js
new file mode 100644
index 000000000000..38c62aef2679
--- /dev/null
+++ b/core/modules/color/preview.es6.js
@@ -0,0 +1,74 @@
+/**
+ * @file
+ * Attaches preview-related behavior for the Color module.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * Namespace for color-related functionality for Drupal.
+   *
+   * @namespace
+   */
+  Drupal.color = {
+
+    /**
+     * The callback for when the color preview has been attached.
+     *
+     * @param {Element} context
+     *   The context to initiate the color behaviour.
+     * @param {object} settings
+     *   Settings for the color functionality.
+     * @param {HTMLFormElement} form
+     *   The form to initiate the color behaviour on.
+     * @param {object} farb
+     *   The farbtastic object.
+     * @param {number} height
+     *   Height of gradient.
+     * @param {number} width
+     *   Width of gradient.
+     */
+    callback: function (context, settings, form, farb, height, width) {
+      var accum;
+      var delta;
+      // Solid background.
+      form.find('.color-preview').css('backgroundColor', form.find('.color-palette input[name="palette[base]"]').val());
+
+      // Text preview.
+      form.find('#text').css('color', form.find('.color-palette input[name="palette[text]"]').val());
+      form.find('#text a, #text h2').css('color', form.find('.color-palette input[name="palette[link]"]').val());
+
+      function gradientLineColor(i, element) {
+        for (var k in accum) {
+          if (accum.hasOwnProperty(k)) {
+            accum[k] += delta[k];
+          }
+        }
+        element.style.backgroundColor = farb.pack(accum);
+      }
+
+      // Set up gradients if there are some.
+      var color_start;
+      var color_end;
+      for (var i in settings.gradients) {
+        if (settings.gradients.hasOwnProperty(i)) {
+          color_start = farb.unpack(form.find('.color-palette input[name="palette[' + settings.gradients[i].colors[0] + ']"]').val());
+          color_end = farb.unpack(form.find('.color-palette input[name="palette[' + settings.gradients[i].colors[1] + ']"]').val());
+          if (color_start && color_end) {
+            delta = [];
+            for (var j in color_start) {
+              if (color_start.hasOwnProperty(j)) {
+                delta[j] = (color_end[j] - color_start[j]) / (settings.gradients[i].vertical ? height[i] : width[i]);
+              }
+            }
+            accum = color_start;
+            // Render gradient lines.
+            form.find('#gradient-' + i + ' > div').each(gradientLineColor);
+          }
+        }
+      }
+    }
+  };
+})(jQuery, Drupal);
diff --git a/core/modules/color/preview.js b/core/modules/color/preview.js
index 38c62aef2679..a303bab1e7b4 100644
--- a/core/modules/color/preview.js
+++ b/core/modules/color/preview.js
@@ -1,42 +1,22 @@
 /**
- * @file
- * Attaches preview-related behavior for the Color module.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/color/preview.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   * Namespace for color-related functionality for Drupal.
-   *
-   * @namespace
-   */
   Drupal.color = {
-
-    /**
-     * The callback for when the color preview has been attached.
-     *
-     * @param {Element} context
-     *   The context to initiate the color behaviour.
-     * @param {object} settings
-     *   Settings for the color functionality.
-     * @param {HTMLFormElement} form
-     *   The form to initiate the color behaviour on.
-     * @param {object} farb
-     *   The farbtastic object.
-     * @param {number} height
-     *   Height of gradient.
-     * @param {number} width
-     *   Width of gradient.
-     */
-    callback: function (context, settings, form, farb, height, width) {
+    callback: function callback(context, settings, form, farb, height, width) {
       var accum;
       var delta;
-      // Solid background.
+
       form.find('.color-preview').css('backgroundColor', form.find('.color-palette input[name="palette[base]"]').val());
 
-      // Text preview.
       form.find('#text').css('color', form.find('.color-palette input[name="palette[text]"]').val());
       form.find('#text a, #text h2').css('color', form.find('.color-palette input[name="palette[link]"]').val());
 
@@ -49,7 +29,6 @@
         element.style.backgroundColor = farb.pack(accum);
       }
 
-      // Set up gradients if there are some.
       var color_start;
       var color_end;
       for (var i in settings.gradients) {
@@ -64,11 +43,11 @@
               }
             }
             accum = color_start;
-            // Render gradient lines.
+
             form.find('#gradient-' + i + ' > div').each(gradientLineColor);
           }
         }
       }
     }
   };
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/modules/color/tests/modules/color_test/themes/color_test_theme/js/color_test_theme-fontsize.es6.js b/core/modules/color/tests/modules/color_test/themes/color_test_theme/js/color_test_theme-fontsize.es6.js
new file mode 100644
index 000000000000..232428164a04
--- /dev/null
+++ b/core/modules/color/tests/modules/color_test/themes/color_test_theme/js/color_test_theme-fontsize.es6.js
@@ -0,0 +1,9 @@
+/**
+ * @file
+ * Adds javascript functions for font resizing.
+ */
+(function ($) {
+  'use strict';
+
+  $(document).ready(function () {});
+})(jQuery);
diff --git a/core/modules/color/tests/modules/color_test/themes/color_test_theme/js/color_test_theme-fontsize.js b/core/modules/color/tests/modules/color_test/themes/color_test_theme/js/color_test_theme-fontsize.js
index 232428164a04..6881f780bb1f 100644
--- a/core/modules/color/tests/modules/color_test/themes/color_test_theme/js/color_test_theme-fontsize.js
+++ b/core/modules/color/tests/modules/color_test/themes/color_test_theme/js/color_test_theme-fontsize.js
@@ -1,9 +1,13 @@
 /**
- * @file
- * Adds javascript functions for font resizing.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/color/tests/modules/color_test/themes/color_test_theme/js/color_test_theme-fontsize.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
+
 (function ($) {
   'use strict';
 
   $(document).ready(function () {});
-})(jQuery);
+})(jQuery);
\ No newline at end of file
diff --git a/core/modules/comment/comment-entity-form.es6.js b/core/modules/comment/comment-entity-form.es6.js
new file mode 100644
index 000000000000..fd16d8bfb0e4
--- /dev/null
+++ b/core/modules/comment/comment-entity-form.es6.js
@@ -0,0 +1,23 @@
+/**
+ * @file
+ * Attaches comment behaviors to the entity form.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   *
+   * @type {Drupal~behavior}
+   */
+  Drupal.behaviors.commentFieldsetSummaries = {
+    attach: function (context) {
+      var $context = $(context);
+      $context.find('fieldset.comment-entity-settings-form').drupalSetSummary(function (context) {
+        return Drupal.checkPlain($(context).find('.js-form-item-comment input:checked').next('label').text());
+      });
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/core/modules/comment/comment-entity-form.js b/core/modules/comment/comment-entity-form.js
index fd16d8bfb0e4..8121e464b3df 100644
--- a/core/modules/comment/comment-entity-form.js
+++ b/core/modules/comment/comment-entity-form.js
@@ -1,23 +1,21 @@
 /**
- * @file
- * Attaches comment behaviors to the entity form.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/comment/comment-entity-form.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   *
-   * @type {Drupal~behavior}
-   */
   Drupal.behaviors.commentFieldsetSummaries = {
-    attach: function (context) {
+    attach: function attach(context) {
       var $context = $(context);
       $context.find('fieldset.comment-entity-settings-form').drupalSetSummary(function (context) {
         return Drupal.checkPlain($(context).find('.js-form-item-comment input:checked').next('label').text());
       });
     }
   };
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/modules/comment/js/comment-by-viewer.es6.js b/core/modules/comment/js/comment-by-viewer.es6.js
new file mode 100644
index 000000000000..22d4b54561e4
--- /dev/null
+++ b/core/modules/comment/js/comment-by-viewer.es6.js
@@ -0,0 +1,26 @@
+/**
+ * @file
+ * Attaches behaviors for the Comment module's "by-viewer" class.
+ */
+
+(function ($, Drupal, drupalSettings) {
+
+  'use strict';
+
+  /**
+   * Add 'by-viewer' class to comments written by the current user.
+   *
+   * @type {Drupal~behavior}
+   */
+  Drupal.behaviors.commentByViewer = {
+    attach: function (context) {
+      var currentUserID = parseInt(drupalSettings.user.uid, 10);
+      $('[data-comment-user-id]')
+        .filter(function () {
+          return parseInt(this.getAttribute('data-comment-user-id'), 10) === currentUserID;
+        })
+        .addClass('by-viewer');
+    }
+  };
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/comment/js/comment-by-viewer.js b/core/modules/comment/js/comment-by-viewer.js
index 22d4b54561e4..360ab3bfcfcd 100644
--- a/core/modules/comment/js/comment-by-viewer.js
+++ b/core/modules/comment/js/comment-by-viewer.js
@@ -1,26 +1,21 @@
 /**
- * @file
- * Attaches behaviors for the Comment module's "by-viewer" class.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/comment/js/comment-by-viewer.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings) {
 
   'use strict';
 
-  /**
-   * Add 'by-viewer' class to comments written by the current user.
-   *
-   * @type {Drupal~behavior}
-   */
   Drupal.behaviors.commentByViewer = {
-    attach: function (context) {
+    attach: function attach(context) {
       var currentUserID = parseInt(drupalSettings.user.uid, 10);
-      $('[data-comment-user-id]')
-        .filter(function () {
-          return parseInt(this.getAttribute('data-comment-user-id'), 10) === currentUserID;
-        })
-        .addClass('by-viewer');
+      $('[data-comment-user-id]').filter(function () {
+        return parseInt(this.getAttribute('data-comment-user-id'), 10) === currentUserID;
+      }).addClass('by-viewer');
     }
   };
-
-})(jQuery, Drupal, drupalSettings);
+})(jQuery, Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/modules/comment/js/comment-new-indicator.es6.js b/core/modules/comment/js/comment-new-indicator.es6.js
new file mode 100644
index 000000000000..d5f0a4f45c88
--- /dev/null
+++ b/core/modules/comment/js/comment-new-indicator.es6.js
@@ -0,0 +1,96 @@
+/**
+ * @file
+ * Attaches behaviors for the Comment module's "new" indicator.
+ *
+ * May only be loaded for authenticated users, with the History module
+ * installed.
+ */
+
+(function ($, Drupal, window) {
+
+  'use strict';
+
+  /**
+   * Renders "new" comment indicators wherever necessary.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches "new" comment indicators behavior.
+   */
+  Drupal.behaviors.commentNewIndicator = {
+    attach: function (context) {
+      // Collect all "new" comment indicator placeholders (and their
+      // corresponding node IDs) newer than 30 days ago that have not already
+      // been read after their last comment timestamp.
+      var nodeIDs = [];
+      var $placeholders = $(context)
+        .find('[data-comment-timestamp]')
+        .once('history')
+        .filter(function () {
+          var $placeholder = $(this);
+          var commentTimestamp = parseInt($placeholder.attr('data-comment-timestamp'), 10);
+          var nodeID = $placeholder.closest('[data-history-node-id]').attr('data-history-node-id');
+          if (Drupal.history.needsServerCheck(nodeID, commentTimestamp)) {
+            nodeIDs.push(nodeID);
+            return true;
+          }
+          else {
+            return false;
+          }
+        });
+
+      if ($placeholders.length === 0) {
+        return;
+      }
+
+      // Fetch the node read timestamps from the server.
+      Drupal.history.fetchTimestamps(nodeIDs, function () {
+        processCommentNewIndicators($placeholders);
+      });
+    }
+  };
+
+  /**
+   * Processes the markup for "new comment" indicators.
+   *
+   * @param {jQuery} $placeholders
+   *   The elements that should be processed.
+   */
+  function processCommentNewIndicators($placeholders) {
+    var isFirstNewComment = true;
+    var newCommentString = Drupal.t('new');
+    var $placeholder;
+
+    $placeholders.each(function (index, placeholder) {
+      $placeholder = $(placeholder);
+      var timestamp = parseInt($placeholder.attr('data-comment-timestamp'), 10);
+      var $node = $placeholder.closest('[data-history-node-id]');
+      var nodeID = $node.attr('data-history-node-id');
+      var lastViewTimestamp = Drupal.history.getLastRead(nodeID);
+
+      if (timestamp > lastViewTimestamp) {
+        // Turn the placeholder into an actual "new" indicator.
+        var $comment = $(placeholder)
+          .removeClass('hidden')
+          .text(newCommentString)
+          .closest('.js-comment')
+          // Add 'new' class to the comment, so it can be styled.
+          .addClass('new');
+
+        // Insert "new" anchor just before the "comment-<cid>" anchor if
+        // this is the first new comment in the DOM.
+        if (isFirstNewComment) {
+          isFirstNewComment = false;
+          $comment.prev().before('<a id="new" />');
+          // If the URL points to the first new comment, then scroll to that
+          // comment.
+          if (window.location.hash === '#new') {
+            window.scrollTo(0, $comment.offset().top - Drupal.displace.offsets.top);
+          }
+        }
+      }
+    });
+  }
+
+})(jQuery, Drupal, window);
diff --git a/core/modules/comment/js/comment-new-indicator.js b/core/modules/comment/js/comment-new-indicator.js
index d5f0a4f45c88..b896b5df6c86 100644
--- a/core/modules/comment/js/comment-new-indicator.js
+++ b/core/modules/comment/js/comment-new-indicator.js
@@ -1,62 +1,40 @@
 /**
- * @file
- * Attaches behaviors for the Comment module's "new" indicator.
- *
- * May only be loaded for authenticated users, with the History module
- * installed.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/comment/js/comment-new-indicator.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, window) {
 
   'use strict';
 
-  /**
-   * Renders "new" comment indicators wherever necessary.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches "new" comment indicators behavior.
-   */
   Drupal.behaviors.commentNewIndicator = {
-    attach: function (context) {
-      // Collect all "new" comment indicator placeholders (and their
-      // corresponding node IDs) newer than 30 days ago that have not already
-      // been read after their last comment timestamp.
+    attach: function attach(context) {
       var nodeIDs = [];
-      var $placeholders = $(context)
-        .find('[data-comment-timestamp]')
-        .once('history')
-        .filter(function () {
-          var $placeholder = $(this);
-          var commentTimestamp = parseInt($placeholder.attr('data-comment-timestamp'), 10);
-          var nodeID = $placeholder.closest('[data-history-node-id]').attr('data-history-node-id');
-          if (Drupal.history.needsServerCheck(nodeID, commentTimestamp)) {
-            nodeIDs.push(nodeID);
-            return true;
-          }
-          else {
-            return false;
-          }
-        });
+      var $placeholders = $(context).find('[data-comment-timestamp]').once('history').filter(function () {
+        var $placeholder = $(this);
+        var commentTimestamp = parseInt($placeholder.attr('data-comment-timestamp'), 10);
+        var nodeID = $placeholder.closest('[data-history-node-id]').attr('data-history-node-id');
+        if (Drupal.history.needsServerCheck(nodeID, commentTimestamp)) {
+          nodeIDs.push(nodeID);
+          return true;
+        } else {
+          return false;
+        }
+      });
 
       if ($placeholders.length === 0) {
         return;
       }
 
-      // Fetch the node read timestamps from the server.
       Drupal.history.fetchTimestamps(nodeIDs, function () {
         processCommentNewIndicators($placeholders);
       });
     }
   };
 
-  /**
-   * Processes the markup for "new comment" indicators.
-   *
-   * @param {jQuery} $placeholders
-   *   The elements that should be processed.
-   */
   function processCommentNewIndicators($placeholders) {
     var isFirstNewComment = true;
     var newCommentString = Drupal.t('new');
@@ -70,21 +48,12 @@
       var lastViewTimestamp = Drupal.history.getLastRead(nodeID);
 
       if (timestamp > lastViewTimestamp) {
-        // Turn the placeholder into an actual "new" indicator.
-        var $comment = $(placeholder)
-          .removeClass('hidden')
-          .text(newCommentString)
-          .closest('.js-comment')
-          // Add 'new' class to the comment, so it can be styled.
-          .addClass('new');
+        var $comment = $(placeholder).removeClass('hidden').text(newCommentString).closest('.js-comment').addClass('new');
 
-        // Insert "new" anchor just before the "comment-<cid>" anchor if
-        // this is the first new comment in the DOM.
         if (isFirstNewComment) {
           isFirstNewComment = false;
           $comment.prev().before('<a id="new" />');
-          // If the URL points to the first new comment, then scroll to that
-          // comment.
+
           if (window.location.hash === '#new') {
             window.scrollTo(0, $comment.offset().top - Drupal.displace.offsets.top);
           }
@@ -92,5 +61,4 @@
       }
     });
   }
-
-})(jQuery, Drupal, window);
+})(jQuery, Drupal, window);
\ No newline at end of file
diff --git a/core/modules/comment/js/node-new-comments-link.es6.js b/core/modules/comment/js/node-new-comments-link.es6.js
new file mode 100644
index 000000000000..291bcbd11613
--- /dev/null
+++ b/core/modules/comment/js/node-new-comments-link.es6.js
@@ -0,0 +1,177 @@
+/**
+ * @file
+ * Attaches behaviors for the Comment module's "X new comments" link.
+ *
+ * May only be loaded for authenticated users, with the History module
+ * installed.
+ */
+
+(function ($, Drupal, drupalSettings) {
+
+  'use strict';
+
+  /**
+   * Render "X new comments" links wherever necessary.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches new comment links behavior.
+   */
+  Drupal.behaviors.nodeNewCommentsLink = {
+    attach: function (context) {
+      // Collect all "X new comments" node link placeholders (and their
+      // corresponding node IDs) newer than 30 days ago that have not already
+      // been read after their last comment timestamp.
+      var nodeIDs = [];
+      var $placeholders = $(context)
+        .find('[data-history-node-last-comment-timestamp]')
+        .once('history')
+        .filter(function () {
+          var $placeholder = $(this);
+          var lastCommentTimestamp = parseInt($placeholder.attr('data-history-node-last-comment-timestamp'), 10);
+          var nodeID = $placeholder.closest('[data-history-node-id]').attr('data-history-node-id');
+          if (Drupal.history.needsServerCheck(nodeID, lastCommentTimestamp)) {
+            nodeIDs.push(nodeID);
+            // Hide this placeholder link until it is certain we'll need it.
+            hide($placeholder);
+            return true;
+          }
+          else {
+            // Remove this placeholder link from the DOM because we won't need
+            // it.
+            remove($placeholder);
+            return false;
+          }
+        });
+
+      if ($placeholders.length === 0) {
+        return;
+      }
+
+      // Perform an AJAX request to retrieve node read timestamps.
+      Drupal.history.fetchTimestamps(nodeIDs, function () {
+        processNodeNewCommentLinks($placeholders);
+      });
+    }
+  };
+
+  /**
+   * Hides a "new comment" element.
+   *
+   * @param {jQuery} $placeholder
+   *   The placeholder element of the new comment link.
+   *
+   * @return {jQuery}
+   *   The placeholder element passed in as a parameter.
+   */
+  function hide($placeholder) {
+    return $placeholder
+      // Find the parent <li>.
+      .closest('.comment-new-comments')
+      // Find the preceding <li>, if any, and give it the 'last' class.
+      .prev().addClass('last')
+      // Go back to the parent <li> and hide it.
+      .end().hide();
+  }
+
+  /**
+   * Removes a "new comment" element.
+   *
+   * @param {jQuery} $placeholder
+   *   The placeholder element of the new comment link.
+   */
+  function remove($placeholder) {
+    hide($placeholder).remove();
+  }
+
+  /**
+   * Shows a "new comment" element.
+   *
+   * @param {jQuery} $placeholder
+   *   The placeholder element of the new comment link.
+   *
+   * @return {jQuery}
+   *   The placeholder element passed in as a parameter.
+   */
+  function show($placeholder) {
+    return $placeholder
+      // Find the parent <li>.
+      .closest('.comment-new-comments')
+      // Find the preceding <li>, if any, and remove its 'last' class, if any.
+      .prev().removeClass('last')
+      // Go back to the parent <li> and show it.
+      .end().show();
+  }
+
+  /**
+   * Processes new comment links and adds appropriate text in relevant cases.
+   *
+   * @param {jQuery} $placeholders
+   *   The placeholder elements of the current page.
+   */
+  function processNodeNewCommentLinks($placeholders) {
+    // Figure out which placeholders need the "x new comments" links.
+    var $placeholdersToUpdate = {};
+    var fieldName = 'comment';
+    var $placeholder;
+    $placeholders.each(function (index, placeholder) {
+      $placeholder = $(placeholder);
+      var timestamp = parseInt($placeholder.attr('data-history-node-last-comment-timestamp'), 10);
+      fieldName = $placeholder.attr('data-history-node-field-name');
+      var nodeID = $placeholder.closest('[data-history-node-id]').attr('data-history-node-id');
+      var lastViewTimestamp = Drupal.history.getLastRead(nodeID);
+
+      // Queue this placeholder's "X new comments" link to be downloaded from
+      // the server.
+      if (timestamp > lastViewTimestamp) {
+        $placeholdersToUpdate[nodeID] = $placeholder;
+      }
+      // No "X new comments" link necessary; remove it from the DOM.
+      else {
+        remove($placeholder);
+      }
+    });
+
+    // Perform an AJAX request to retrieve node view timestamps.
+    var nodeIDs = Object.keys($placeholdersToUpdate);
+    if (nodeIDs.length === 0) {
+      return;
+    }
+
+    /**
+     * Renders the "X new comments" links.
+     *
+     * Either use the data embedded in the page or perform an AJAX request to
+     * retrieve the same data.
+     *
+     * @param {object} results
+     *   Data about new comment links indexed by nodeID.
+     */
+    function render(results) {
+      for (var nodeID in results) {
+        if (results.hasOwnProperty(nodeID) && $placeholdersToUpdate.hasOwnProperty(nodeID)) {
+          $placeholdersToUpdate[nodeID]
+            .attr('href', results[nodeID].first_new_comment_link)
+            .text(Drupal.formatPlural(results[nodeID].new_comment_count, '1 new comment', '@count new comments'))
+            .removeClass('hidden');
+          show($placeholdersToUpdate[nodeID]);
+        }
+      }
+    }
+
+    if (drupalSettings.comment && drupalSettings.comment.newCommentsLinks) {
+      render(drupalSettings.comment.newCommentsLinks.node[fieldName]);
+    }
+    else {
+      $.ajax({
+        url: Drupal.url('comments/render_new_comments_node_links'),
+        type: 'POST',
+        data: {'node_ids[]': nodeIDs, 'field_name': fieldName},
+        dataType: 'json',
+        success: render
+      });
+    }
+  }
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/comment/js/node-new-comments-link.js b/core/modules/comment/js/node-new-comments-link.js
index 291bcbd11613..ae3cd121f094 100644
--- a/core/modules/comment/js/node-new-comments-link.js
+++ b/core/modules/comment/js/node-new-comments-link.js
@@ -1,117 +1,56 @@
 /**
- * @file
- * Attaches behaviors for the Comment module's "X new comments" link.
- *
- * May only be loaded for authenticated users, with the History module
- * installed.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/comment/js/node-new-comments-link.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings) {
 
   'use strict';
 
-  /**
-   * Render "X new comments" links wherever necessary.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches new comment links behavior.
-   */
   Drupal.behaviors.nodeNewCommentsLink = {
-    attach: function (context) {
-      // Collect all "X new comments" node link placeholders (and their
-      // corresponding node IDs) newer than 30 days ago that have not already
-      // been read after their last comment timestamp.
+    attach: function attach(context) {
       var nodeIDs = [];
-      var $placeholders = $(context)
-        .find('[data-history-node-last-comment-timestamp]')
-        .once('history')
-        .filter(function () {
-          var $placeholder = $(this);
-          var lastCommentTimestamp = parseInt($placeholder.attr('data-history-node-last-comment-timestamp'), 10);
-          var nodeID = $placeholder.closest('[data-history-node-id]').attr('data-history-node-id');
-          if (Drupal.history.needsServerCheck(nodeID, lastCommentTimestamp)) {
-            nodeIDs.push(nodeID);
-            // Hide this placeholder link until it is certain we'll need it.
-            hide($placeholder);
-            return true;
-          }
-          else {
-            // Remove this placeholder link from the DOM because we won't need
-            // it.
-            remove($placeholder);
-            return false;
-          }
-        });
+      var $placeholders = $(context).find('[data-history-node-last-comment-timestamp]').once('history').filter(function () {
+        var $placeholder = $(this);
+        var lastCommentTimestamp = parseInt($placeholder.attr('data-history-node-last-comment-timestamp'), 10);
+        var nodeID = $placeholder.closest('[data-history-node-id]').attr('data-history-node-id');
+        if (Drupal.history.needsServerCheck(nodeID, lastCommentTimestamp)) {
+          nodeIDs.push(nodeID);
+
+          hide($placeholder);
+          return true;
+        } else {
+          remove($placeholder);
+          return false;
+        }
+      });
 
       if ($placeholders.length === 0) {
         return;
       }
 
-      // Perform an AJAX request to retrieve node read timestamps.
       Drupal.history.fetchTimestamps(nodeIDs, function () {
         processNodeNewCommentLinks($placeholders);
       });
     }
   };
 
-  /**
-   * Hides a "new comment" element.
-   *
-   * @param {jQuery} $placeholder
-   *   The placeholder element of the new comment link.
-   *
-   * @return {jQuery}
-   *   The placeholder element passed in as a parameter.
-   */
   function hide($placeholder) {
-    return $placeholder
-      // Find the parent <li>.
-      .closest('.comment-new-comments')
-      // Find the preceding <li>, if any, and give it the 'last' class.
-      .prev().addClass('last')
-      // Go back to the parent <li> and hide it.
-      .end().hide();
+    return $placeholder.closest('.comment-new-comments').prev().addClass('last').end().hide();
   }
 
-  /**
-   * Removes a "new comment" element.
-   *
-   * @param {jQuery} $placeholder
-   *   The placeholder element of the new comment link.
-   */
   function remove($placeholder) {
     hide($placeholder).remove();
   }
 
-  /**
-   * Shows a "new comment" element.
-   *
-   * @param {jQuery} $placeholder
-   *   The placeholder element of the new comment link.
-   *
-   * @return {jQuery}
-   *   The placeholder element passed in as a parameter.
-   */
   function show($placeholder) {
-    return $placeholder
-      // Find the parent <li>.
-      .closest('.comment-new-comments')
-      // Find the preceding <li>, if any, and remove its 'last' class, if any.
-      .prev().removeClass('last')
-      // Go back to the parent <li> and show it.
-      .end().show();
+    return $placeholder.closest('.comment-new-comments').prev().removeClass('last').end().show();
   }
 
-  /**
-   * Processes new comment links and adds appropriate text in relevant cases.
-   *
-   * @param {jQuery} $placeholders
-   *   The placeholder elements of the current page.
-   */
   function processNodeNewCommentLinks($placeholders) {
-    // Figure out which placeholders need the "x new comments" links.
     var $placeholdersToUpdate = {};
     var fieldName = 'comment';
     var $placeholder;
@@ -122,39 +61,22 @@
       var nodeID = $placeholder.closest('[data-history-node-id]').attr('data-history-node-id');
       var lastViewTimestamp = Drupal.history.getLastRead(nodeID);
 
-      // Queue this placeholder's "X new comments" link to be downloaded from
-      // the server.
       if (timestamp > lastViewTimestamp) {
         $placeholdersToUpdate[nodeID] = $placeholder;
-      }
-      // No "X new comments" link necessary; remove it from the DOM.
-      else {
-        remove($placeholder);
-      }
+      } else {
+          remove($placeholder);
+        }
     });
 
-    // Perform an AJAX request to retrieve node view timestamps.
     var nodeIDs = Object.keys($placeholdersToUpdate);
     if (nodeIDs.length === 0) {
       return;
     }
 
-    /**
-     * Renders the "X new comments" links.
-     *
-     * Either use the data embedded in the page or perform an AJAX request to
-     * retrieve the same data.
-     *
-     * @param {object} results
-     *   Data about new comment links indexed by nodeID.
-     */
     function render(results) {
       for (var nodeID in results) {
         if (results.hasOwnProperty(nodeID) && $placeholdersToUpdate.hasOwnProperty(nodeID)) {
-          $placeholdersToUpdate[nodeID]
-            .attr('href', results[nodeID].first_new_comment_link)
-            .text(Drupal.formatPlural(results[nodeID].new_comment_count, '1 new comment', '@count new comments'))
-            .removeClass('hidden');
+          $placeholdersToUpdate[nodeID].attr('href', results[nodeID].first_new_comment_link).text(Drupal.formatPlural(results[nodeID].new_comment_count, '1 new comment', '@count new comments')).removeClass('hidden');
           show($placeholdersToUpdate[nodeID]);
         }
       }
@@ -162,16 +84,14 @@
 
     if (drupalSettings.comment && drupalSettings.comment.newCommentsLinks) {
       render(drupalSettings.comment.newCommentsLinks.node[fieldName]);
-    }
-    else {
+    } else {
       $.ajax({
         url: Drupal.url('comments/render_new_comments_node_links'),
         type: 'POST',
-        data: {'node_ids[]': nodeIDs, 'field_name': fieldName},
+        data: { 'node_ids[]': nodeIDs, 'field_name': fieldName },
         dataType: 'json',
         success: render
       });
     }
   }
-
-})(jQuery, Drupal, drupalSettings);
+})(jQuery, Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/modules/content_translation/content_translation.admin.es6.js b/core/modules/content_translation/content_translation.admin.es6.js
new file mode 100644
index 000000000000..fcbf699f7382
--- /dev/null
+++ b/core/modules/content_translation/content_translation.admin.es6.js
@@ -0,0 +1,131 @@
+/**
+ * @file
+ * Content Translation admin behaviors.
+ */
+
+(function ($, Drupal, drupalSettings) {
+
+  'use strict';
+
+  /**
+   * Forces applicable options to be checked as translatable.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches content translation dependent options to the UI.
+   */
+  Drupal.behaviors.contentTranslationDependentOptions = {
+    attach: function (context) {
+      var $context = $(context);
+      var options = drupalSettings.contentTranslationDependentOptions;
+      var $fields;
+      var dependent_columns;
+
+      function fieldsChangeHandler($fields, dependent_columns) {
+        return function (e) {
+          Drupal.behaviors.contentTranslationDependentOptions.check($fields, dependent_columns, $(e.target));
+        };
+      }
+
+      // We're given a generic name to look for so we find all inputs containing
+      // that name and copy over the input values that require all columns to be
+      // translatable.
+      if (options && options.dependent_selectors) {
+        for (var field in options.dependent_selectors) {
+          if (options.dependent_selectors.hasOwnProperty(field)) {
+            $fields = $context.find('input[name^="' + field + '"]');
+            dependent_columns = options.dependent_selectors[field];
+
+            $fields.on('change', fieldsChangeHandler($fields, dependent_columns));
+            Drupal.behaviors.contentTranslationDependentOptions.check($fields, dependent_columns);
+          }
+        }
+      }
+    },
+    check: function ($fields, dependent_columns, $changed) {
+      var $element = $changed;
+      var column;
+
+      function filterFieldsList(index, field) {
+        return $(field).val() === column;
+      }
+
+      // A field that has many different translatable parts can also define one
+      // or more columns that require all columns to be translatable.
+      for (var index in dependent_columns) {
+        if (dependent_columns.hasOwnProperty(index)) {
+          column = dependent_columns[index];
+
+          if (!$changed) {
+            $element = $fields.filter(filterFieldsList);
+          }
+
+          if ($element.is('input[value="' + column + '"]:checked')) {
+            $fields.prop('checked', true)
+              .not($element).prop('disabled', true);
+          }
+          else {
+            $fields.prop('disabled', false);
+          }
+
+        }
+      }
+    }
+  };
+
+  /**
+   * Makes field translatability inherit bundle translatability.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches content translation behavior.
+   */
+  Drupal.behaviors.contentTranslation = {
+    attach: function (context) {
+      // Initially hide all field rows for non translatable bundles and all
+      // column rows for non translatable fields.
+      $(context).find('table .bundle-settings .translatable :input').once('translation-entity-admin-hide').each(function () {
+        var $input = $(this);
+        var $bundleSettings = $input.closest('.bundle-settings');
+        if (!$input.is(':checked')) {
+          $bundleSettings.nextUntil('.bundle-settings').hide();
+        }
+        else {
+          $bundleSettings.nextUntil('.bundle-settings', '.field-settings').find('.translatable :input:not(:checked)').closest('.field-settings').nextUntil(':not(.column-settings)').hide();
+        }
+      });
+
+      // When a bundle is made translatable all of its fields should inherit
+      // this setting. Instead when it is made non translatable its fields are
+      // hidden, since their translatability no longer matters.
+      $('body').once('translation-entity-admin-bind').on('click', 'table .bundle-settings .translatable :input', function (e) {
+        var $target = $(e.target);
+        var $bundleSettings = $target.closest('.bundle-settings');
+        var $settings = $bundleSettings.nextUntil('.bundle-settings');
+        var $fieldSettings = $settings.filter('.field-settings');
+        if ($target.is(':checked')) {
+          $bundleSettings.find('.operations :input[name$="[language_alterable]"]').prop('checked', true);
+          $fieldSettings.find('.translatable :input').prop('checked', true);
+          $settings.show();
+        }
+        else {
+          $settings.hide();
+        }
+      })
+        .on('click', 'table .field-settings .translatable :input', function (e) {
+          var $target = $(e.target);
+          var $fieldSettings = $target.closest('.field-settings');
+          var $columnSettings = $fieldSettings.nextUntil('.field-settings, .bundle-settings');
+          if ($target.is(':checked')) {
+            $columnSettings.show();
+          }
+          else {
+            $columnSettings.hide();
+          }
+        });
+    }
+  };
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/content_translation/content_translation.admin.js b/core/modules/content_translation/content_translation.admin.js
index fcbf699f7382..74ef925b77f2 100644
--- a/core/modules/content_translation/content_translation.admin.js
+++ b/core/modules/content_translation/content_translation.admin.js
@@ -1,22 +1,17 @@
 /**
- * @file
- * Content Translation admin behaviors.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/content_translation/content_translation.admin.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings) {
 
   'use strict';
 
-  /**
-   * Forces applicable options to be checked as translatable.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches content translation dependent options to the UI.
-   */
   Drupal.behaviors.contentTranslationDependentOptions = {
-    attach: function (context) {
+    attach: function attach(context) {
       var $context = $(context);
       var options = drupalSettings.contentTranslationDependentOptions;
       var $fields;
@@ -28,9 +23,6 @@
         };
       }
 
-      // We're given a generic name to look for so we find all inputs containing
-      // that name and copy over the input values that require all columns to be
-      // translatable.
       if (options && options.dependent_selectors) {
         for (var field in options.dependent_selectors) {
           if (options.dependent_selectors.hasOwnProperty(field)) {
@@ -43,7 +35,7 @@
         }
       }
     },
-    check: function ($fields, dependent_columns, $changed) {
+    check: function check($fields, dependent_columns, $changed) {
       var $element = $changed;
       var column;
 
@@ -51,8 +43,6 @@
         return $(field).val() === column;
       }
 
-      // A field that has many different translatable parts can also define one
-      // or more columns that require all columns to be translatable.
       for (var index in dependent_columns) {
         if (dependent_columns.hasOwnProperty(index)) {
           column = dependent_columns[index];
@@ -62,44 +52,27 @@
           }
 
           if ($element.is('input[value="' + column + '"]:checked')) {
-            $fields.prop('checked', true)
-              .not($element).prop('disabled', true);
-          }
-          else {
+            $fields.prop('checked', true).not($element).prop('disabled', true);
+          } else {
             $fields.prop('disabled', false);
           }
-
         }
       }
     }
   };
 
-  /**
-   * Makes field translatability inherit bundle translatability.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches content translation behavior.
-   */
   Drupal.behaviors.contentTranslation = {
-    attach: function (context) {
-      // Initially hide all field rows for non translatable bundles and all
-      // column rows for non translatable fields.
+    attach: function attach(context) {
       $(context).find('table .bundle-settings .translatable :input').once('translation-entity-admin-hide').each(function () {
         var $input = $(this);
         var $bundleSettings = $input.closest('.bundle-settings');
         if (!$input.is(':checked')) {
           $bundleSettings.nextUntil('.bundle-settings').hide();
-        }
-        else {
+        } else {
           $bundleSettings.nextUntil('.bundle-settings', '.field-settings').find('.translatable :input:not(:checked)').closest('.field-settings').nextUntil(':not(.column-settings)').hide();
         }
       });
 
-      // When a bundle is made translatable all of its fields should inherit
-      // this setting. Instead when it is made non translatable its fields are
-      // hidden, since their translatability no longer matters.
       $('body').once('translation-entity-admin-bind').on('click', 'table .bundle-settings .translatable :input', function (e) {
         var $target = $(e.target);
         var $bundleSettings = $target.closest('.bundle-settings');
@@ -109,23 +82,19 @@
           $bundleSettings.find('.operations :input[name$="[language_alterable]"]').prop('checked', true);
           $fieldSettings.find('.translatable :input').prop('checked', true);
           $settings.show();
-        }
-        else {
+        } else {
           $settings.hide();
         }
-      })
-        .on('click', 'table .field-settings .translatable :input', function (e) {
-          var $target = $(e.target);
-          var $fieldSettings = $target.closest('.field-settings');
-          var $columnSettings = $fieldSettings.nextUntil('.field-settings, .bundle-settings');
-          if ($target.is(':checked')) {
-            $columnSettings.show();
-          }
-          else {
-            $columnSettings.hide();
-          }
-        });
+      }).on('click', 'table .field-settings .translatable :input', function (e) {
+        var $target = $(e.target);
+        var $fieldSettings = $target.closest('.field-settings');
+        var $columnSettings = $fieldSettings.nextUntil('.field-settings, .bundle-settings');
+        if ($target.is(':checked')) {
+          $columnSettings.show();
+        } else {
+          $columnSettings.hide();
+        }
+      });
     }
   };
-
-})(jQuery, Drupal, drupalSettings);
+})(jQuery, Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/modules/contextual/js/contextual.es6.js b/core/modules/contextual/js/contextual.es6.js
new file mode 100644
index 000000000000..558ea105cc2f
--- /dev/null
+++ b/core/modules/contextual/js/contextual.es6.js
@@ -0,0 +1,256 @@
+/**
+ * @file
+ * Attaches behaviors for the Contextual module.
+ */
+
+(function ($, Drupal, drupalSettings, _, Backbone, JSON, storage) {
+
+  'use strict';
+
+  var options = $.extend(drupalSettings.contextual,
+    // Merge strings on top of drupalSettings so that they are not mutable.
+    {
+      strings: {
+        open: Drupal.t('Open'),
+        close: Drupal.t('Close')
+      }
+    }
+  );
+
+  // Clear the cached contextual links whenever the current user's set of
+  // permissions changes.
+  var cachedPermissionsHash = storage.getItem('Drupal.contextual.permissionsHash');
+  var permissionsHash = drupalSettings.user.permissionsHash;
+  if (cachedPermissionsHash !== permissionsHash) {
+    if (typeof permissionsHash === 'string') {
+      _.chain(storage).keys().each(function (key) {
+        if (key.substring(0, 18) === 'Drupal.contextual.') {
+          storage.removeItem(key);
+        }
+      });
+    }
+    storage.setItem('Drupal.contextual.permissionsHash', permissionsHash);
+  }
+
+  /**
+   * Initializes a contextual link: updates its DOM, sets up model and views.
+   *
+   * @param {jQuery} $contextual
+   *   A contextual links placeholder DOM element, containing the actual
+   *   contextual links as rendered by the server.
+   * @param {string} html
+   *   The server-side rendered HTML for this contextual link.
+   */
+  function initContextual($contextual, html) {
+    var $region = $contextual.closest('.contextual-region');
+    var contextual = Drupal.contextual;
+
+    $contextual
+      // Update the placeholder to contain its rendered contextual links.
+      .html(html)
+      // Use the placeholder as a wrapper with a specific class to provide
+      // positioning and behavior attachment context.
+      .addClass('contextual')
+      // Ensure a trigger element exists before the actual contextual links.
+      .prepend(Drupal.theme('contextualTrigger'));
+
+    // Set the destination parameter on each of the contextual links.
+    var destination = 'destination=' + Drupal.encodePath(drupalSettings.path.currentPath);
+    $contextual.find('.contextual-links a').each(function () {
+      var url = this.getAttribute('href');
+      var glue = (url.indexOf('?') === -1) ? '?' : '&';
+      this.setAttribute('href', url + glue + destination);
+    });
+
+    // Create a model and the appropriate views.
+    var model = new contextual.StateModel({
+      title: $region.find('h2').eq(0).text().trim()
+    });
+    var viewOptions = $.extend({el: $contextual, model: model}, options);
+    contextual.views.push({
+      visual: new contextual.VisualView(viewOptions),
+      aural: new contextual.AuralView(viewOptions),
+      keyboard: new contextual.KeyboardView(viewOptions)
+    });
+    contextual.regionViews.push(new contextual.RegionView(
+      $.extend({el: $region, model: model}, options))
+    );
+
+    // Add the model to the collection. This must happen after the views have
+    // been associated with it, otherwise collection change event handlers can't
+    // trigger the model change event handler in its views.
+    contextual.collection.add(model);
+
+    // Let other JavaScript react to the adding of a new contextual link.
+    $(document).trigger('drupalContextualLinkAdded', {
+      $el: $contextual,
+      $region: $region,
+      model: model
+    });
+
+    // Fix visual collisions between contextual link triggers.
+    adjustIfNestedAndOverlapping($contextual);
+  }
+
+  /**
+   * Determines if a contextual link is nested & overlapping, if so: adjusts it.
+   *
+   * This only deals with two levels of nesting; deeper levels are not touched.
+   *
+   * @param {jQuery} $contextual
+   *   A contextual links placeholder DOM element, containing the actual
+   *   contextual links as rendered by the server.
+   */
+  function adjustIfNestedAndOverlapping($contextual) {
+    var $contextuals = $contextual
+      // @todo confirm that .closest() is not sufficient
+      .parents('.contextual-region').eq(-1)
+      .find('.contextual');
+
+    // Early-return when there's no nesting.
+    if ($contextuals.length === 1) {
+      return;
+    }
+
+    // If the two contextual links overlap, then we move the second one.
+    var firstTop = $contextuals.eq(0).offset().top;
+    var secondTop = $contextuals.eq(1).offset().top;
+    if (firstTop === secondTop) {
+      var $nestedContextual = $contextuals.eq(1);
+
+      // Retrieve height of nested contextual link.
+      var height = 0;
+      var $trigger = $nestedContextual.find('.trigger');
+      // Elements with the .visually-hidden class have no dimensions, so this
+      // class must be temporarily removed to the calculate the height.
+      $trigger.removeClass('visually-hidden');
+      height = $nestedContextual.height();
+      $trigger.addClass('visually-hidden');
+
+      // Adjust nested contextual link's position.
+      $nestedContextual.css({top: $nestedContextual.position().top + height});
+    }
+  }
+
+  /**
+   * Attaches outline behavior for regions associated with contextual links.
+   *
+   * Events
+   *   Contextual triggers an event that can be used by other scripts.
+   *   - drupalContextualLinkAdded: Triggered when a contextual link is added.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *  Attaches the outline behavior to the right context.
+   */
+  Drupal.behaviors.contextual = {
+    attach: function (context) {
+      var $context = $(context);
+
+      // Find all contextual links placeholders, if any.
+      var $placeholders = $context.find('[data-contextual-id]').once('contextual-render');
+      if ($placeholders.length === 0) {
+        return;
+      }
+
+      // Collect the IDs for all contextual links placeholders.
+      var ids = [];
+      $placeholders.each(function () {
+        ids.push($(this).attr('data-contextual-id'));
+      });
+
+      // Update all contextual links placeholders whose HTML is cached.
+      var uncachedIDs = _.filter(ids, function initIfCached(contextualID) {
+        var html = storage.getItem('Drupal.contextual.' + contextualID);
+        if (html && html.length) {
+          // Initialize after the current execution cycle, to make the AJAX
+          // request for retrieving the uncached contextual links as soon as
+          // possible, but also to ensure that other Drupal behaviors have had
+          // the chance to set up an event listener on the Backbone collection
+          // Drupal.contextual.collection.
+          window.setTimeout(function () {
+            initContextual($context.find('[data-contextual-id="' + contextualID + '"]'), html);
+          });
+          return false;
+        }
+        return true;
+      });
+
+      // Perform an AJAX request to let the server render the contextual links
+      // for each of the placeholders.
+      if (uncachedIDs.length > 0) {
+        $.ajax({
+          url: Drupal.url('contextual/render'),
+          type: 'POST',
+          data: {'ids[]': uncachedIDs},
+          dataType: 'json',
+          success: function (results) {
+            _.each(results, function (html, contextualID) {
+              // Store the metadata.
+              storage.setItem('Drupal.contextual.' + contextualID, html);
+              // If the rendered contextual links are empty, then the current
+              // user does not have permission to access the associated links:
+              // don't render anything.
+              if (html.length > 0) {
+                // Update the placeholders to contain its rendered contextual
+                // links. Usually there will only be one placeholder, but it's
+                // possible for multiple identical placeholders exist on the
+                // page (probably because the same content appears more than
+                // once).
+                $placeholders = $context.find('[data-contextual-id="' + contextualID + '"]');
+
+                // Initialize the contextual links.
+                for (var i = 0; i < $placeholders.length; i++) {
+                  initContextual($placeholders.eq(i), html);
+                }
+              }
+            });
+          }
+        });
+      }
+    }
+  };
+
+  /**
+   * Namespace for contextual related functionality.
+   *
+   * @namespace
+   */
+  Drupal.contextual = {
+
+    /**
+     * The {@link Drupal.contextual.View} instances associated with each list
+     * element of contextual links.
+     *
+     * @type {Array}
+     */
+    views: [],
+
+    /**
+     * The {@link Drupal.contextual.RegionView} instances associated with each
+     * contextual region element.
+     *
+     * @type {Array}
+     */
+    regionViews: []
+  };
+
+  /**
+   * A Backbone.Collection of {@link Drupal.contextual.StateModel} instances.
+   *
+   * @type {Backbone.Collection}
+   */
+  Drupal.contextual.collection = new Backbone.Collection([], {model: Drupal.contextual.StateModel});
+
+  /**
+   * A trigger is an interactive element often bound to a click handler.
+   *
+   * @return {string}
+   *   A string representing a DOM fragment.
+   */
+  Drupal.theme.contextualTrigger = function () {
+    return '<button class="trigger visually-hidden focusable" type="button"></button>';
+  };
+
+})(jQuery, Drupal, drupalSettings, _, Backbone, window.JSON, window.sessionStorage);
diff --git a/core/modules/contextual/js/contextual.js b/core/modules/contextual/js/contextual.js
index 558ea105cc2f..7eb42ec3de0d 100644
--- a/core/modules/contextual/js/contextual.js
+++ b/core/modules/contextual/js/contextual.js
@@ -1,24 +1,22 @@
 /**
- * @file
- * Attaches behaviors for the Contextual module.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/contextual/js/contextual.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings, _, Backbone, JSON, storage) {
 
   'use strict';
 
-  var options = $.extend(drupalSettings.contextual,
-    // Merge strings on top of drupalSettings so that they are not mutable.
-    {
-      strings: {
-        open: Drupal.t('Open'),
-        close: Drupal.t('Close')
-      }
+  var options = $.extend(drupalSettings.contextual, {
+    strings: {
+      open: Drupal.t('Open'),
+      close: Drupal.t('Close')
     }
-  );
+  });
 
-  // Clear the cached contextual links whenever the current user's set of
-  // permissions changes.
   var cachedPermissionsHash = storage.getItem('Drupal.contextual.permissionsHash');
   var permissionsHash = drupalSettings.user.permissionsHash;
   if (cachedPermissionsHash !== permissionsHash) {
@@ -32,143 +30,81 @@
     storage.setItem('Drupal.contextual.permissionsHash', permissionsHash);
   }
 
-  /**
-   * Initializes a contextual link: updates its DOM, sets up model and views.
-   *
-   * @param {jQuery} $contextual
-   *   A contextual links placeholder DOM element, containing the actual
-   *   contextual links as rendered by the server.
-   * @param {string} html
-   *   The server-side rendered HTML for this contextual link.
-   */
   function initContextual($contextual, html) {
     var $region = $contextual.closest('.contextual-region');
     var contextual = Drupal.contextual;
 
-    $contextual
-      // Update the placeholder to contain its rendered contextual links.
-      .html(html)
-      // Use the placeholder as a wrapper with a specific class to provide
-      // positioning and behavior attachment context.
-      .addClass('contextual')
-      // Ensure a trigger element exists before the actual contextual links.
-      .prepend(Drupal.theme('contextualTrigger'));
+    $contextual.html(html).addClass('contextual').prepend(Drupal.theme('contextualTrigger'));
 
-    // Set the destination parameter on each of the contextual links.
     var destination = 'destination=' + Drupal.encodePath(drupalSettings.path.currentPath);
     $contextual.find('.contextual-links a').each(function () {
       var url = this.getAttribute('href');
-      var glue = (url.indexOf('?') === -1) ? '?' : '&';
+      var glue = url.indexOf('?') === -1 ? '?' : '&';
       this.setAttribute('href', url + glue + destination);
     });
 
-    // Create a model and the appropriate views.
     var model = new contextual.StateModel({
       title: $region.find('h2').eq(0).text().trim()
     });
-    var viewOptions = $.extend({el: $contextual, model: model}, options);
+    var viewOptions = $.extend({ el: $contextual, model: model }, options);
     contextual.views.push({
       visual: new contextual.VisualView(viewOptions),
       aural: new contextual.AuralView(viewOptions),
       keyboard: new contextual.KeyboardView(viewOptions)
     });
-    contextual.regionViews.push(new contextual.RegionView(
-      $.extend({el: $region, model: model}, options))
-    );
+    contextual.regionViews.push(new contextual.RegionView($.extend({ el: $region, model: model }, options)));
 
-    // Add the model to the collection. This must happen after the views have
-    // been associated with it, otherwise collection change event handlers can't
-    // trigger the model change event handler in its views.
     contextual.collection.add(model);
 
-    // Let other JavaScript react to the adding of a new contextual link.
     $(document).trigger('drupalContextualLinkAdded', {
       $el: $contextual,
       $region: $region,
       model: model
     });
 
-    // Fix visual collisions between contextual link triggers.
     adjustIfNestedAndOverlapping($contextual);
   }
 
-  /**
-   * Determines if a contextual link is nested & overlapping, if so: adjusts it.
-   *
-   * This only deals with two levels of nesting; deeper levels are not touched.
-   *
-   * @param {jQuery} $contextual
-   *   A contextual links placeholder DOM element, containing the actual
-   *   contextual links as rendered by the server.
-   */
   function adjustIfNestedAndOverlapping($contextual) {
-    var $contextuals = $contextual
-      // @todo confirm that .closest() is not sufficient
-      .parents('.contextual-region').eq(-1)
-      .find('.contextual');
+    var $contextuals = $contextual.parents('.contextual-region').eq(-1).find('.contextual');
 
-    // Early-return when there's no nesting.
     if ($contextuals.length === 1) {
       return;
     }
 
-    // If the two contextual links overlap, then we move the second one.
     var firstTop = $contextuals.eq(0).offset().top;
     var secondTop = $contextuals.eq(1).offset().top;
     if (firstTop === secondTop) {
       var $nestedContextual = $contextuals.eq(1);
 
-      // Retrieve height of nested contextual link.
       var height = 0;
       var $trigger = $nestedContextual.find('.trigger');
-      // Elements with the .visually-hidden class have no dimensions, so this
-      // class must be temporarily removed to the calculate the height.
+
       $trigger.removeClass('visually-hidden');
       height = $nestedContextual.height();
       $trigger.addClass('visually-hidden');
 
-      // Adjust nested contextual link's position.
-      $nestedContextual.css({top: $nestedContextual.position().top + height});
+      $nestedContextual.css({ top: $nestedContextual.position().top + height });
     }
   }
 
-  /**
-   * Attaches outline behavior for regions associated with contextual links.
-   *
-   * Events
-   *   Contextual triggers an event that can be used by other scripts.
-   *   - drupalContextualLinkAdded: Triggered when a contextual link is added.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *  Attaches the outline behavior to the right context.
-   */
   Drupal.behaviors.contextual = {
-    attach: function (context) {
+    attach: function attach(context) {
       var $context = $(context);
 
-      // Find all contextual links placeholders, if any.
       var $placeholders = $context.find('[data-contextual-id]').once('contextual-render');
       if ($placeholders.length === 0) {
         return;
       }
 
-      // Collect the IDs for all contextual links placeholders.
       var ids = [];
       $placeholders.each(function () {
         ids.push($(this).attr('data-contextual-id'));
       });
 
-      // Update all contextual links placeholders whose HTML is cached.
       var uncachedIDs = _.filter(ids, function initIfCached(contextualID) {
         var html = storage.getItem('Drupal.contextual.' + contextualID);
         if (html && html.length) {
-          // Initialize after the current execution cycle, to make the AJAX
-          // request for retrieving the uncached contextual links as soon as
-          // possible, but also to ensure that other Drupal behaviors have had
-          // the chance to set up an event listener on the Backbone collection
-          // Drupal.contextual.collection.
           window.setTimeout(function () {
             initContextual($context.find('[data-contextual-id="' + contextualID + '"]'), html);
           });
@@ -177,30 +113,19 @@
         return true;
       });
 
-      // Perform an AJAX request to let the server render the contextual links
-      // for each of the placeholders.
       if (uncachedIDs.length > 0) {
         $.ajax({
           url: Drupal.url('contextual/render'),
           type: 'POST',
-          data: {'ids[]': uncachedIDs},
+          data: { 'ids[]': uncachedIDs },
           dataType: 'json',
-          success: function (results) {
+          success: function success(results) {
             _.each(results, function (html, contextualID) {
-              // Store the metadata.
               storage.setItem('Drupal.contextual.' + contextualID, html);
-              // If the rendered contextual links are empty, then the current
-              // user does not have permission to access the associated links:
-              // don't render anything.
+
               if (html.length > 0) {
-                // Update the placeholders to contain its rendered contextual
-                // links. Usually there will only be one placeholder, but it's
-                // possible for multiple identical placeholders exist on the
-                // page (probably because the same content appears more than
-                // once).
                 $placeholders = $context.find('[data-contextual-id="' + contextualID + '"]');
 
-                // Initialize the contextual links.
                 for (var i = 0; i < $placeholders.length; i++) {
                   initContextual($placeholders.eq(i), html);
                 }
@@ -212,45 +137,15 @@
     }
   };
 
-  /**
-   * Namespace for contextual related functionality.
-   *
-   * @namespace
-   */
   Drupal.contextual = {
-
-    /**
-     * The {@link Drupal.contextual.View} instances associated with each list
-     * element of contextual links.
-     *
-     * @type {Array}
-     */
     views: [],
 
-    /**
-     * The {@link Drupal.contextual.RegionView} instances associated with each
-     * contextual region element.
-     *
-     * @type {Array}
-     */
     regionViews: []
   };
 
-  /**
-   * A Backbone.Collection of {@link Drupal.contextual.StateModel} instances.
-   *
-   * @type {Backbone.Collection}
-   */
-  Drupal.contextual.collection = new Backbone.Collection([], {model: Drupal.contextual.StateModel});
+  Drupal.contextual.collection = new Backbone.Collection([], { model: Drupal.contextual.StateModel });
 
-  /**
-   * A trigger is an interactive element often bound to a click handler.
-   *
-   * @return {string}
-   *   A string representing a DOM fragment.
-   */
   Drupal.theme.contextualTrigger = function () {
     return '<button class="trigger visually-hidden focusable" type="button"></button>';
   };
-
-})(jQuery, Drupal, drupalSettings, _, Backbone, window.JSON, window.sessionStorage);
+})(jQuery, Drupal, drupalSettings, _, Backbone, window.JSON, window.sessionStorage);
\ No newline at end of file
diff --git a/core/modules/contextual/js/contextual.toolbar.es6.js b/core/modules/contextual/js/contextual.toolbar.es6.js
new file mode 100644
index 000000000000..b5a9053490c0
--- /dev/null
+++ b/core/modules/contextual/js/contextual.toolbar.es6.js
@@ -0,0 +1,77 @@
+/**
+ * @file
+ * Attaches behaviors for the Contextual module's edit toolbar tab.
+ */
+
+(function ($, Drupal, Backbone) {
+
+  'use strict';
+
+  var strings = {
+    tabbingReleased: Drupal.t('Tabbing is no longer constrained by the Contextual module.'),
+    tabbingConstrained: Drupal.t('Tabbing is constrained to a set of @contextualsCount and the edit mode toggle.'),
+    pressEsc: Drupal.t('Press the esc key to exit.')
+  };
+
+  /**
+   * Initializes a contextual link: updates its DOM, sets up model and views.
+   *
+   * @param {HTMLElement} context
+   *   A contextual links DOM element as rendered by the server.
+   */
+  function initContextualToolbar(context) {
+    if (!Drupal.contextual || !Drupal.contextual.collection) {
+      return;
+    }
+
+    var contextualToolbar = Drupal.contextualToolbar;
+    var model = contextualToolbar.model = new contextualToolbar.StateModel({
+      // Checks whether localStorage indicates we should start in edit mode
+      // rather than view mode.
+      // @see Drupal.contextualToolbar.VisualView.persist
+      isViewing: localStorage.getItem('Drupal.contextualToolbar.isViewing') !== 'false'
+    }, {
+      contextualCollection: Drupal.contextual.collection
+    });
+
+    var viewOptions = {
+      el: $('.toolbar .toolbar-bar .contextual-toolbar-tab'),
+      model: model,
+      strings: strings
+    };
+    new contextualToolbar.VisualView(viewOptions);
+    new contextualToolbar.AuralView(viewOptions);
+  }
+
+  /**
+   * Attaches contextual's edit toolbar tab behavior.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches contextual toolbar behavior on a contextualToolbar-init event.
+   */
+  Drupal.behaviors.contextualToolbar = {
+    attach: function (context) {
+      if ($('body').once('contextualToolbar-init').length) {
+        initContextualToolbar(context);
+      }
+    }
+  };
+
+  /**
+   * Namespace for the contextual toolbar.
+   *
+   * @namespace
+   */
+  Drupal.contextualToolbar = {
+
+    /**
+     * The {@link Drupal.contextualToolbar.StateModel} instance.
+     *
+     * @type {?Drupal.contextualToolbar.StateModel}
+     */
+    model: null
+  };
+
+})(jQuery, Drupal, Backbone);
diff --git a/core/modules/contextual/js/contextual.toolbar.js b/core/modules/contextual/js/contextual.toolbar.js
index b5a9053490c0..291a5a68c300 100644
--- a/core/modules/contextual/js/contextual.toolbar.js
+++ b/core/modules/contextual/js/contextual.toolbar.js
@@ -1,7 +1,10 @@
 /**
- * @file
- * Attaches behaviors for the Contextual module's edit toolbar tab.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/contextual/js/contextual.toolbar.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, Backbone) {
 
@@ -13,12 +16,6 @@
     pressEsc: Drupal.t('Press the esc key to exit.')
   };
 
-  /**
-   * Initializes a contextual link: updates its DOM, sets up model and views.
-   *
-   * @param {HTMLElement} context
-   *   A contextual links DOM element as rendered by the server.
-   */
   function initContextualToolbar(context) {
     if (!Drupal.contextual || !Drupal.contextual.collection) {
       return;
@@ -26,9 +23,6 @@
 
     var contextualToolbar = Drupal.contextualToolbar;
     var model = contextualToolbar.model = new contextualToolbar.StateModel({
-      // Checks whether localStorage indicates we should start in edit mode
-      // rather than view mode.
-      // @see Drupal.contextualToolbar.VisualView.persist
       isViewing: localStorage.getItem('Drupal.contextualToolbar.isViewing') !== 'false'
     }, {
       contextualCollection: Drupal.contextual.collection
@@ -43,35 +37,15 @@
     new contextualToolbar.AuralView(viewOptions);
   }
 
-  /**
-   * Attaches contextual's edit toolbar tab behavior.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches contextual toolbar behavior on a contextualToolbar-init event.
-   */
   Drupal.behaviors.contextualToolbar = {
-    attach: function (context) {
+    attach: function attach(context) {
       if ($('body').once('contextualToolbar-init').length) {
         initContextualToolbar(context);
       }
     }
   };
 
-  /**
-   * Namespace for the contextual toolbar.
-   *
-   * @namespace
-   */
   Drupal.contextualToolbar = {
-
-    /**
-     * The {@link Drupal.contextualToolbar.StateModel} instance.
-     *
-     * @type {?Drupal.contextualToolbar.StateModel}
-     */
     model: null
   };
-
-})(jQuery, Drupal, Backbone);
+})(jQuery, Drupal, Backbone);
\ No newline at end of file
diff --git a/core/modules/contextual/js/models/StateModel.es6.js b/core/modules/contextual/js/models/StateModel.es6.js
new file mode 100644
index 000000000000..465d717d5636
--- /dev/null
+++ b/core/modules/contextual/js/models/StateModel.es6.js
@@ -0,0 +1,132 @@
+/**
+ * @file
+ * A Backbone Model for the state of a contextual link's trigger, list & region.
+ */
+
+(function (Drupal, Backbone) {
+
+  'use strict';
+
+  /**
+   * Models the state of a contextual link's trigger, list & region.
+   *
+   * @constructor
+   *
+   * @augments Backbone.Model
+   */
+  Drupal.contextual.StateModel = Backbone.Model.extend(/** @lends Drupal.contextual.StateModel# */{
+
+    /**
+     * @type {object}
+     *
+     * @prop {string} title
+     * @prop {bool} regionIsHovered
+     * @prop {bool} hasFocus
+     * @prop {bool} isOpen
+     * @prop {bool} isLocked
+     */
+    defaults: /** @lends Drupal.contextual.StateModel# */{
+
+      /**
+       * The title of the entity to which these contextual links apply.
+       *
+       * @type {string}
+       */
+      title: '',
+
+      /**
+       * Represents if the contextual region is being hovered.
+       *
+       * @type {bool}
+       */
+      regionIsHovered: false,
+
+      /**
+       * Represents if the contextual trigger or options have focus.
+       *
+       * @type {bool}
+       */
+      hasFocus: false,
+
+      /**
+       * Represents if the contextual options for an entity are available to
+       * be selected (i.e. whether the list of options is visible).
+       *
+       * @type {bool}
+       */
+      isOpen: false,
+
+      /**
+       * When the model is locked, the trigger remains active.
+       *
+       * @type {bool}
+       */
+      isLocked: false
+    },
+
+    /**
+     * Opens or closes the contextual link.
+     *
+     * If it is opened, then also give focus.
+     *
+     * @return {Drupal.contextual.StateModel}
+     *   The current contextual state model.
+     */
+    toggleOpen: function () {
+      var newIsOpen = !this.get('isOpen');
+      this.set('isOpen', newIsOpen);
+      if (newIsOpen) {
+        this.focus();
+      }
+      return this;
+    },
+
+    /**
+     * Closes this contextual link.
+     *
+     * Does not call blur() because we want to allow a contextual link to have
+     * focus, yet be closed for example when hovering.
+     *
+     * @return {Drupal.contextual.StateModel}
+     *   The current contextual state model.
+     */
+    close: function () {
+      this.set('isOpen', false);
+      return this;
+    },
+
+    /**
+     * Gives focus to this contextual link.
+     *
+     * Also closes + removes focus from every other contextual link.
+     *
+     * @return {Drupal.contextual.StateModel}
+     *   The current contextual state model.
+     */
+    focus: function () {
+      this.set('hasFocus', true);
+      var cid = this.cid;
+      this.collection.each(function (model) {
+        if (model.cid !== cid) {
+          model.close().blur();
+        }
+      });
+      return this;
+    },
+
+    /**
+     * Removes focus from this contextual link, unless it is open.
+     *
+     * @return {Drupal.contextual.StateModel}
+     *   The current contextual state model.
+     */
+    blur: function () {
+      if (!this.get('isOpen')) {
+        this.set('hasFocus', false);
+      }
+      return this;
+    }
+
+  });
+
+})(Drupal, Backbone);
diff --git a/core/modules/contextual/js/models/StateModel.js b/core/modules/contextual/js/models/StateModel.js
index 465d717d5636..ac9a89582486 100644
--- a/core/modules/contextual/js/models/StateModel.js
+++ b/core/modules/contextual/js/models/StateModel.js
@@ -1,78 +1,29 @@
 /**
- * @file
- * A Backbone Model for the state of a contextual link's trigger, list & region.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/contextual/js/models/StateModel.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function (Drupal, Backbone) {
 
   'use strict';
 
-  /**
-   * Models the state of a contextual link's trigger, list & region.
-   *
-   * @constructor
-   *
-   * @augments Backbone.Model
-   */
-  Drupal.contextual.StateModel = Backbone.Model.extend(/** @lends Drupal.contextual.StateModel# */{
-
-    /**
-     * @type {object}
-     *
-     * @prop {string} title
-     * @prop {bool} regionIsHovered
-     * @prop {bool} hasFocus
-     * @prop {bool} isOpen
-     * @prop {bool} isLocked
-     */
-    defaults: /** @lends Drupal.contextual.StateModel# */{
-
-      /**
-       * The title of the entity to which these contextual links apply.
-       *
-       * @type {string}
-       */
+  Drupal.contextual.StateModel = Backbone.Model.extend({
+    defaults: {
       title: '',
 
-      /**
-       * Represents if the contextual region is being hovered.
-       *
-       * @type {bool}
-       */
       regionIsHovered: false,
 
-      /**
-       * Represents if the contextual trigger or options have focus.
-       *
-       * @type {bool}
-       */
       hasFocus: false,
 
-      /**
-       * Represents if the contextual options for an entity are available to
-       * be selected (i.e. whether the list of options is visible).
-       *
-       * @type {bool}
-       */
       isOpen: false,
 
-      /**
-       * When the model is locked, the trigger remains active.
-       *
-       * @type {bool}
-       */
       isLocked: false
     },
 
-    /**
-     * Opens or closes the contextual link.
-     *
-     * If it is opened, then also give focus.
-     *
-     * @return {Drupal.contextual.StateModel}
-     *   The current contextual state model.
-     */
-    toggleOpen: function () {
+    toggleOpen: function toggleOpen() {
       var newIsOpen = !this.get('isOpen');
       this.set('isOpen', newIsOpen);
       if (newIsOpen) {
@@ -81,29 +32,12 @@
       return this;
     },
 
-    /**
-     * Closes this contextual link.
-     *
-     * Does not call blur() because we want to allow a contextual link to have
-     * focus, yet be closed for example when hovering.
-     *
-     * @return {Drupal.contextual.StateModel}
-     *   The current contextual state model.
-     */
-    close: function () {
+    close: function close() {
       this.set('isOpen', false);
       return this;
     },
 
-    /**
-     * Gives focus to this contextual link.
-     *
-     * Also closes + removes focus from every other contextual link.
-     *
-     * @return {Drupal.contextual.StateModel}
-     *   The current contextual state model.
-     */
-    focus: function () {
+    focus: function focus() {
       this.set('hasFocus', true);
       var cid = this.cid;
       this.collection.each(function (model) {
@@ -114,13 +48,7 @@
       return this;
     },
 
-    /**
-     * Removes focus from this contextual link, unless it is open.
-     *
-     * @return {Drupal.contextual.StateModel}
-     *   The current contextual state model.
-     */
-    blur: function () {
+    blur: function blur() {
       if (!this.get('isOpen')) {
         this.set('hasFocus', false);
       }
@@ -128,5 +56,4 @@
     }
 
   });
-
-})(Drupal, Backbone);
+})(Drupal, Backbone);
\ No newline at end of file
diff --git a/core/modules/contextual/js/toolbar/models/StateModel.es6.js b/core/modules/contextual/js/toolbar/models/StateModel.es6.js
new file mode 100644
index 000000000000..d9159e4e97f6
--- /dev/null
+++ b/core/modules/contextual/js/toolbar/models/StateModel.es6.js
@@ -0,0 +1,119 @@
+/**
+ * @file
+ * A Backbone Model for the state of Contextual module's edit toolbar tab.
+ */
+
+(function (Drupal, Backbone) {
+
+  'use strict';
+
+  Drupal.contextualToolbar.StateModel = Backbone.Model.extend(/** @lends Drupal.contextualToolbar.StateModel# */{
+
+    /**
+     * @type {object}
+     *
+     * @prop {bool} isViewing
+     * @prop {bool} isVisible
+     * @prop {number} contextualCount
+     * @prop {Drupal~TabbingContext} tabbingContext
+     */
+    defaults: /** @lends Drupal.contextualToolbar.StateModel# */{
+
+      /**
+       * Indicates whether the toggle is currently in "view" or "edit" mode.
+       *
+       * @type {bool}
+       */
+      isViewing: true,
+
+      /**
+       * Indicates whether the toggle should be visible or hidden. Automatically
+       * calculated, depends on contextualCount.
+       *
+       * @type {bool}
+       */
+      isVisible: false,
+
+      /**
+       * Tracks how many contextual links exist on the page.
+       *
+       * @type {number}
+       */
+      contextualCount: 0,
+
+      /**
+       * A TabbingContext object as returned by {@link Drupal~TabbingManager}:
+       * the set of tabbable elements when edit mode is enabled.
+       *
+       * @type {?Drupal~TabbingContext}
+       */
+      tabbingContext: null
+    },
+
+    /**
+     * Models the state of the edit mode toggle.
+     *
+     * @constructs
+     *
+     * @augments Backbone.Model
+     *
+     * @param {object} attrs
+     *   Attributes for the backbone model.
+     * @param {object} options
+     *   An object with the following option:
+     * @param {Backbone.collection} options.contextualCollection
+     *   The collection of {@link Drupal.contextual.StateModel} models that
+     *   represent the contextual links on the page.
+     */
+    initialize: function (attrs, options) {
+      // Respond to new/removed contextual links.
+      this.listenTo(options.contextualCollection, 'reset remove add', this.countContextualLinks);
+      this.listenTo(options.contextualCollection, 'add', this.lockNewContextualLinks);
+
+      // Automatically determine visibility.
+      this.listenTo(this, 'change:contextualCount', this.updateVisibility);
+
+      // Whenever edit mode is toggled, lock all contextual links.
+      this.listenTo(this, 'change:isViewing', function (model, isViewing) {
+        options.contextualCollection.each(function (contextualModel) {
+          contextualModel.set('isLocked', !isViewing);
+        });
+      });
+    },
+
+    /**
+     * Tracks the number of contextual link models in the collection.
+     *
+     * @param {Drupal.contextual.StateModel} contextualModel
+     *   The contextual links model that was added or removed.
+     * @param {Backbone.Collection} contextualCollection
+     *    The collection of contextual link models.
+     */
+    countContextualLinks: function (contextualModel, contextualCollection) {
+      this.set('contextualCount', contextualCollection.length);
+    },
+
+    /**
+     * Lock newly added contextual links if edit mode is enabled.
+     *
+     * @param {Drupal.contextual.StateModel} contextualModel
+     *   The contextual links model that was added.
+     * @param {Backbone.Collection} [contextualCollection]
+     *    The collection of contextual link models.
+     */
+    lockNewContextualLinks: function (contextualModel, contextualCollection) {
+      if (!this.get('isViewing')) {
+        contextualModel.set('isLocked', true);
+      }
+    },
+
+    /**
+     * Automatically updates visibility of the view/edit mode toggle.
+     */
+    updateVisibility: function () {
+      this.set('isVisible', this.get('contextualCount') > 0);
+    }
+
+  });
+
+})(Drupal, Backbone);
diff --git a/core/modules/contextual/js/toolbar/models/StateModel.js b/core/modules/contextual/js/toolbar/models/StateModel.js
index d9159e4e97f6..93eed97b5b55 100644
--- a/core/modules/contextual/js/toolbar/models/StateModel.js
+++ b/core/modules/contextual/js/toolbar/models/StateModel.js
@@ -1,79 +1,32 @@
 /**
- * @file
- * A Backbone Model for the state of Contextual module's edit toolbar tab.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/contextual/js/toolbar/models/StateModel.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function (Drupal, Backbone) {
 
   'use strict';
 
-  Drupal.contextualToolbar.StateModel = Backbone.Model.extend(/** @lends Drupal.contextualToolbar.StateModel# */{
-
-    /**
-     * @type {object}
-     *
-     * @prop {bool} isViewing
-     * @prop {bool} isVisible
-     * @prop {number} contextualCount
-     * @prop {Drupal~TabbingContext} tabbingContext
-     */
-    defaults: /** @lends Drupal.contextualToolbar.StateModel# */{
-
-      /**
-       * Indicates whether the toggle is currently in "view" or "edit" mode.
-       *
-       * @type {bool}
-       */
+  Drupal.contextualToolbar.StateModel = Backbone.Model.extend({
+    defaults: {
       isViewing: true,
 
-      /**
-       * Indicates whether the toggle should be visible or hidden. Automatically
-       * calculated, depends on contextualCount.
-       *
-       * @type {bool}
-       */
       isVisible: false,
 
-      /**
-       * Tracks how many contextual links exist on the page.
-       *
-       * @type {number}
-       */
       contextualCount: 0,
 
-      /**
-       * A TabbingContext object as returned by {@link Drupal~TabbingManager}:
-       * the set of tabbable elements when edit mode is enabled.
-       *
-       * @type {?Drupal~TabbingContext}
-       */
       tabbingContext: null
     },
 
-    /**
-     * Models the state of the edit mode toggle.
-     *
-     * @constructs
-     *
-     * @augments Backbone.Model
-     *
-     * @param {object} attrs
-     *   Attributes for the backbone model.
-     * @param {object} options
-     *   An object with the following option:
-     * @param {Backbone.collection} options.contextualCollection
-     *   The collection of {@link Drupal.contextual.StateModel} models that
-     *   represent the contextual links on the page.
-     */
-    initialize: function (attrs, options) {
-      // Respond to new/removed contextual links.
+    initialize: function initialize(attrs, options) {
       this.listenTo(options.contextualCollection, 'reset remove add', this.countContextualLinks);
       this.listenTo(options.contextualCollection, 'add', this.lockNewContextualLinks);
 
-      // Automatically determine visibility.
       this.listenTo(this, 'change:contextualCount', this.updateVisibility);
 
-      // Whenever edit mode is toggled, lock all contextual links.
       this.listenTo(this, 'change:isViewing', function (model, isViewing) {
         options.contextualCollection.each(function (contextualModel) {
           contextualModel.set('isLocked', !isViewing);
@@ -81,39 +34,19 @@
       });
     },
 
-    /**
-     * Tracks the number of contextual link models in the collection.
-     *
-     * @param {Drupal.contextual.StateModel} contextualModel
-     *   The contextual links model that was added or removed.
-     * @param {Backbone.Collection} contextualCollection
-     *    The collection of contextual link models.
-     */
-    countContextualLinks: function (contextualModel, contextualCollection) {
+    countContextualLinks: function countContextualLinks(contextualModel, contextualCollection) {
       this.set('contextualCount', contextualCollection.length);
     },
 
-    /**
-     * Lock newly added contextual links if edit mode is enabled.
-     *
-     * @param {Drupal.contextual.StateModel} contextualModel
-     *   The contextual links model that was added.
-     * @param {Backbone.Collection} [contextualCollection]
-     *    The collection of contextual link models.
-     */
-    lockNewContextualLinks: function (contextualModel, contextualCollection) {
+    lockNewContextualLinks: function lockNewContextualLinks(contextualModel, contextualCollection) {
       if (!this.get('isViewing')) {
         contextualModel.set('isLocked', true);
       }
     },
 
-    /**
-     * Automatically updates visibility of the view/edit mode toggle.
-     */
-    updateVisibility: function () {
+    updateVisibility: function updateVisibility() {
       this.set('isVisible', this.get('contextualCount') > 0);
     }
 
   });
-
-})(Drupal, Backbone);
+})(Drupal, Backbone);
\ No newline at end of file
diff --git a/core/modules/contextual/js/toolbar/views/AuralView.es6.js b/core/modules/contextual/js/toolbar/views/AuralView.es6.js
new file mode 100644
index 000000000000..d684ffb9e63e
--- /dev/null
+++ b/core/modules/contextual/js/toolbar/views/AuralView.es6.js
@@ -0,0 +1,104 @@
+/**
+ * @file
+ * A Backbone View that provides the aural view of the edit mode toggle.
+ */
+
+(function ($, Drupal, Backbone, _) {
+
+  'use strict';
+
+  Drupal.contextualToolbar.AuralView = Backbone.View.extend(/** @lends Drupal.contextualToolbar.AuralView# */{
+
+    /**
+     * Tracks whether the tabbing constraint announcement has been read once.
+     *
+     * @type {bool}
+     */
+    announcedOnce: false,
+
+    /**
+     * Renders the aural view of the edit mode toggle (screen reader support).
+     *
+     * @constructs
+     *
+     * @augments Backbone.View
+     *
+     * @param {object} options
+     *   Options for the view.
+     */
+    initialize: function (options) {
+      this.options = options;
+
+      this.listenTo(this.model, 'change', this.render);
+      this.listenTo(this.model, 'change:isViewing', this.manageTabbing);
+
+      $(document).on('keyup', _.bind(this.onKeypress, this));
+    },
+
+    /**
+     * @inheritdoc
+     *
+     * @return {Drupal.contextualToolbar.AuralView}
+     *   The current contextual toolbar aural view.
+     */
+    render: function () {
+      // Render the state.
+      this.$el.find('button').attr('aria-pressed', !this.model.get('isViewing'));
+
+      return this;
+    },
+
+    /**
+     * Limits tabbing to the contextual links and edit mode toolbar tab.
+     */
+    manageTabbing: function () {
+      var tabbingContext = this.model.get('tabbingContext');
+      // Always release an existing tabbing context.
+      if (tabbingContext) {
+        tabbingContext.release();
+        Drupal.announce(this.options.strings.tabbingReleased);
+      }
+      // Create a new tabbing context when edit mode is enabled.
+      if (!this.model.get('isViewing')) {
+        tabbingContext = Drupal.tabbingManager.constrain($('.contextual-toolbar-tab, .contextual'));
+        this.model.set('tabbingContext', tabbingContext);
+        this.announceTabbingConstraint();
+        this.announcedOnce = true;
+      }
+    },
+
+    /**
+     * Announces the current tabbing constraint.
+     */
+    announceTabbingConstraint: function () {
+      var strings = this.options.strings;
+      Drupal.announce(Drupal.formatString(strings.tabbingConstrained, {
+        '@contextualsCount': Drupal.formatPlural(Drupal.contextual.collection.length, '@count contextual link', '@count contextual links')
+      }));
+      Drupal.announce(strings.pressEsc);
+    },
+
+    /**
+     * Responds to esc and tab key press events.
+     *
+     * @param {jQuery.Event} event
+     *   The keypress event.
+     */
+    onKeypress: function (event) {
+      // The first tab key press is tracked so that an annoucement about tabbing
+      // constraints can be raised if edit mode is enabled when the page is
+      // loaded.
+      if (!this.announcedOnce && event.keyCode === 9 && !this.model.get('isViewing')) {
+        this.announceTabbingConstraint();
+        // Set announce to true so that this conditional block won't run again.
+        this.announcedOnce = true;
+      }
+      // Respond to the ESC key. Exit out of edit mode.
+      if (event.keyCode === 27) {
+        this.model.set('isViewing', true);
+      }
+    }
+
+  });
+
+})(jQuery, Drupal, Backbone, _);
diff --git a/core/modules/contextual/js/toolbar/views/AuralView.js b/core/modules/contextual/js/toolbar/views/AuralView.js
index d684ffb9e63e..132342f5801e 100644
--- a/core/modules/contextual/js/toolbar/views/AuralView.js
+++ b/core/modules/contextual/js/toolbar/views/AuralView.js
@@ -1,32 +1,19 @@
 /**
- * @file
- * A Backbone View that provides the aural view of the edit mode toggle.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/contextual/js/toolbar/views/AuralView.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, Backbone, _) {
 
   'use strict';
 
-  Drupal.contextualToolbar.AuralView = Backbone.View.extend(/** @lends Drupal.contextualToolbar.AuralView# */{
-
-    /**
-     * Tracks whether the tabbing constraint announcement has been read once.
-     *
-     * @type {bool}
-     */
+  Drupal.contextualToolbar.AuralView = Backbone.View.extend({
     announcedOnce: false,
 
-    /**
-     * Renders the aural view of the edit mode toggle (screen reader support).
-     *
-     * @constructs
-     *
-     * @augments Backbone.View
-     *
-     * @param {object} options
-     *   Options for the view.
-     */
-    initialize: function (options) {
+    initialize: function initialize(options) {
       this.options = options;
 
       this.listenTo(this.model, 'change', this.render);
@@ -35,30 +22,20 @@
       $(document).on('keyup', _.bind(this.onKeypress, this));
     },
 
-    /**
-     * @inheritdoc
-     *
-     * @return {Drupal.contextualToolbar.AuralView}
-     *   The current contextual toolbar aural view.
-     */
-    render: function () {
-      // Render the state.
+    render: function render() {
       this.$el.find('button').attr('aria-pressed', !this.model.get('isViewing'));
 
       return this;
     },
 
-    /**
-     * Limits tabbing to the contextual links and edit mode toolbar tab.
-     */
-    manageTabbing: function () {
+    manageTabbing: function manageTabbing() {
       var tabbingContext = this.model.get('tabbingContext');
-      // Always release an existing tabbing context.
+
       if (tabbingContext) {
         tabbingContext.release();
         Drupal.announce(this.options.strings.tabbingReleased);
       }
-      // Create a new tabbing context when edit mode is enabled.
+
       if (!this.model.get('isViewing')) {
         tabbingContext = Drupal.tabbingManager.constrain($('.contextual-toolbar-tab, .contextual'));
         this.model.set('tabbingContext', tabbingContext);
@@ -67,10 +44,7 @@
       }
     },
 
-    /**
-     * Announces the current tabbing constraint.
-     */
-    announceTabbingConstraint: function () {
+    announceTabbingConstraint: function announceTabbingConstraint() {
       var strings = this.options.strings;
       Drupal.announce(Drupal.formatString(strings.tabbingConstrained, {
         '@contextualsCount': Drupal.formatPlural(Drupal.contextual.collection.length, '@count contextual link', '@count contextual links')
@@ -78,27 +52,17 @@
       Drupal.announce(strings.pressEsc);
     },
 
-    /**
-     * Responds to esc and tab key press events.
-     *
-     * @param {jQuery.Event} event
-     *   The keypress event.
-     */
-    onKeypress: function (event) {
-      // The first tab key press is tracked so that an annoucement about tabbing
-      // constraints can be raised if edit mode is enabled when the page is
-      // loaded.
+    onKeypress: function onKeypress(event) {
       if (!this.announcedOnce && event.keyCode === 9 && !this.model.get('isViewing')) {
         this.announceTabbingConstraint();
-        // Set announce to true so that this conditional block won't run again.
+
         this.announcedOnce = true;
       }
-      // Respond to the ESC key. Exit out of edit mode.
+
       if (event.keyCode === 27) {
         this.model.set('isViewing', true);
       }
     }
 
   });
-
-})(jQuery, Drupal, Backbone, _);
+})(jQuery, Drupal, Backbone, _);
\ No newline at end of file
diff --git a/core/modules/contextual/js/toolbar/views/VisualView.es6.js b/core/modules/contextual/js/toolbar/views/VisualView.es6.js
new file mode 100644
index 000000000000..d1d413502d32
--- /dev/null
+++ b/core/modules/contextual/js/toolbar/views/VisualView.es6.js
@@ -0,0 +1,84 @@
+/**
+ * @file
+ * A Backbone View that provides the visual view of the edit mode toggle.
+ */
+
+(function (Drupal, Backbone) {
+
+  'use strict';
+
+  Drupal.contextualToolbar.VisualView = Backbone.View.extend(/** @lends Drupal.contextualToolbar.VisualView# */{
+
+    /**
+     * Events for the Backbone view.
+     *
+     * @return {object}
+     *   A mapping of events to be used in the view.
+     */
+    events: function () {
+      // Prevents delay and simulated mouse events.
+      var touchEndToClick = function (event) {
+        event.preventDefault();
+        event.target.click();
+      };
+
+      return {
+        click: function () {
+          this.model.set('isViewing', !this.model.get('isViewing'));
+        },
+        touchend: touchEndToClick
+      };
+    },
+
+    /**
+     * Renders the visual view of the edit mode toggle.
+     *
+     * Listens to mouse & touch and handles edit mode toggle interactions.
+     *
+     * @constructs
+     *
+     * @augments Backbone.View
+     */
+    initialize: function () {
+      this.listenTo(this.model, 'change', this.render);
+      this.listenTo(this.model, 'change:isViewing', this.persist);
+    },
+
+    /**
+     * @inheritdoc
+     *
+     * @return {Drupal.contextualToolbar.VisualView}
+     *   The current contextual toolbar visual view.
+     */
+    render: function () {
+      // Render the visibility.
+      this.$el.toggleClass('hidden', !this.model.get('isVisible'));
+      // Render the state.
+      this.$el.find('button').toggleClass('is-active', !this.model.get('isViewing'));
+
+      return this;
+    },
+
+    /**
+     * Model change handler; persists the isViewing value to localStorage.
+     *
+     * `isViewing === true` is the default, so only stores in localStorage when
+     * it's not the default value (i.e. false).
+     *
+     * @param {Drupal.contextualToolbar.StateModel} model
+     *   A {@link Drupal.contextualToolbar.StateModel} model.
+     * @param {bool} isViewing
+     *   The value of the isViewing attribute in the model.
+     */
+    persist: function (model, isViewing) {
+      if (!isViewing) {
+        localStorage.setItem('Drupal.contextualToolbar.isViewing', 'false');
+      }
+      else {
+        localStorage.removeItem('Drupal.contextualToolbar.isViewing');
+      }
+    }
+
+  });
+
+})(Drupal, Backbone);
diff --git a/core/modules/contextual/js/toolbar/views/VisualView.js b/core/modules/contextual/js/toolbar/views/VisualView.js
index d1d413502d32..f666a95c2180 100644
--- a/core/modules/contextual/js/toolbar/views/VisualView.js
+++ b/core/modules/contextual/js/toolbar/views/VisualView.js
@@ -1,84 +1,50 @@
 /**
- * @file
- * A Backbone View that provides the visual view of the edit mode toggle.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/contextual/js/toolbar/views/VisualView.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function (Drupal, Backbone) {
 
   'use strict';
 
-  Drupal.contextualToolbar.VisualView = Backbone.View.extend(/** @lends Drupal.contextualToolbar.VisualView# */{
-
-    /**
-     * Events for the Backbone view.
-     *
-     * @return {object}
-     *   A mapping of events to be used in the view.
-     */
-    events: function () {
-      // Prevents delay and simulated mouse events.
-      var touchEndToClick = function (event) {
+  Drupal.contextualToolbar.VisualView = Backbone.View.extend({
+    events: function events() {
+      var touchEndToClick = function touchEndToClick(event) {
         event.preventDefault();
         event.target.click();
       };
 
       return {
-        click: function () {
+        click: function click() {
           this.model.set('isViewing', !this.model.get('isViewing'));
         },
         touchend: touchEndToClick
       };
     },
 
-    /**
-     * Renders the visual view of the edit mode toggle.
-     *
-     * Listens to mouse & touch and handles edit mode toggle interactions.
-     *
-     * @constructs
-     *
-     * @augments Backbone.View
-     */
-    initialize: function () {
+    initialize: function initialize() {
       this.listenTo(this.model, 'change', this.render);
       this.listenTo(this.model, 'change:isViewing', this.persist);
     },
 
-    /**
-     * @inheritdoc
-     *
-     * @return {Drupal.contextualToolbar.VisualView}
-     *   The current contextual toolbar visual view.
-     */
-    render: function () {
-      // Render the visibility.
+    render: function render() {
       this.$el.toggleClass('hidden', !this.model.get('isVisible'));
-      // Render the state.
+
       this.$el.find('button').toggleClass('is-active', !this.model.get('isViewing'));
 
       return this;
     },
 
-    /**
-     * Model change handler; persists the isViewing value to localStorage.
-     *
-     * `isViewing === true` is the default, so only stores in localStorage when
-     * it's not the default value (i.e. false).
-     *
-     * @param {Drupal.contextualToolbar.StateModel} model
-     *   A {@link Drupal.contextualToolbar.StateModel} model.
-     * @param {bool} isViewing
-     *   The value of the isViewing attribute in the model.
-     */
-    persist: function (model, isViewing) {
+    persist: function persist(model, isViewing) {
       if (!isViewing) {
         localStorage.setItem('Drupal.contextualToolbar.isViewing', 'false');
-      }
-      else {
+      } else {
         localStorage.removeItem('Drupal.contextualToolbar.isViewing');
       }
     }
 
   });
-
-})(Drupal, Backbone);
+})(Drupal, Backbone);
\ No newline at end of file
diff --git a/core/modules/contextual/js/views/AuralView.es6.js b/core/modules/contextual/js/views/AuralView.es6.js
new file mode 100644
index 000000000000..8ba2e33e347b
--- /dev/null
+++ b/core/modules/contextual/js/views/AuralView.es6.js
@@ -0,0 +1,55 @@
+/**
+ * @file
+ * A Backbone View that provides the aural view of a contextual link.
+ */
+
+(function (Drupal, Backbone) {
+
+  'use strict';
+
+  Drupal.contextual.AuralView = Backbone.View.extend(/** @lends Drupal.contextual.AuralView# */{
+
+    /**
+     * Renders the aural view of a contextual link (i.e. screen reader support).
+     *
+     * @constructs
+     *
+     * @augments Backbone.View
+     *
+     * @param {object} options
+     *   Options for the view.
+     */
+    initialize: function (options) {
+      this.options = options;
+
+      this.listenTo(this.model, 'change', this.render);
+
+      // Use aria-role form so that the number of items in the list is spoken.
+      this.$el.attr('role', 'form');
+
+      // Initial render.
+      this.render();
+    },
+
+    /**
+     * @inheritdoc
+     */
+    render: function () {
+      var isOpen = this.model.get('isOpen');
+
+      // Set the hidden property of the links.
+      this.$el.find('.contextual-links')
+        .prop('hidden', !isOpen);
+
+      // Update the view of the trigger.
+      this.$el.find('.trigger')
+        .text(Drupal.t('@action @title configuration options', {
+          '@action': (!isOpen) ? this.options.strings.open : this.options.strings.close,
+          '@title': this.model.get('title')
+        }))
+        .attr('aria-pressed', isOpen);
+    }
+
+  });
+
+})(Drupal, Backbone);
diff --git a/core/modules/contextual/js/views/AuralView.js b/core/modules/contextual/js/views/AuralView.js
index 8ba2e33e347b..5cbefcdd785d 100644
--- a/core/modules/contextual/js/views/AuralView.js
+++ b/core/modules/contextual/js/views/AuralView.js
@@ -1,55 +1,36 @@
 /**
- * @file
- * A Backbone View that provides the aural view of a contextual link.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/contextual/js/views/AuralView.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function (Drupal, Backbone) {
 
   'use strict';
 
-  Drupal.contextual.AuralView = Backbone.View.extend(/** @lends Drupal.contextual.AuralView# */{
-
-    /**
-     * Renders the aural view of a contextual link (i.e. screen reader support).
-     *
-     * @constructs
-     *
-     * @augments Backbone.View
-     *
-     * @param {object} options
-     *   Options for the view.
-     */
-    initialize: function (options) {
+  Drupal.contextual.AuralView = Backbone.View.extend({
+    initialize: function initialize(options) {
       this.options = options;
 
       this.listenTo(this.model, 'change', this.render);
 
-      // Use aria-role form so that the number of items in the list is spoken.
       this.$el.attr('role', 'form');
 
-      // Initial render.
       this.render();
     },
 
-    /**
-     * @inheritdoc
-     */
-    render: function () {
+    render: function render() {
       var isOpen = this.model.get('isOpen');
 
-      // Set the hidden property of the links.
-      this.$el.find('.contextual-links')
-        .prop('hidden', !isOpen);
-
-      // Update the view of the trigger.
-      this.$el.find('.trigger')
-        .text(Drupal.t('@action @title configuration options', {
-          '@action': (!isOpen) ? this.options.strings.open : this.options.strings.close,
-          '@title': this.model.get('title')
-        }))
-        .attr('aria-pressed', isOpen);
+      this.$el.find('.contextual-links').prop('hidden', !isOpen);
+
+      this.$el.find('.trigger').text(Drupal.t('@action @title configuration options', {
+        '@action': !isOpen ? this.options.strings.open : this.options.strings.close,
+        '@title': this.model.get('title')
+      })).attr('aria-pressed', isOpen);
     }
 
   });
-
-})(Drupal, Backbone);
+})(Drupal, Backbone);
\ No newline at end of file
diff --git a/core/modules/contextual/js/views/KeyboardView.es6.js b/core/modules/contextual/js/views/KeyboardView.es6.js
new file mode 100644
index 000000000000..9c247730e312
--- /dev/null
+++ b/core/modules/contextual/js/views/KeyboardView.es6.js
@@ -0,0 +1,61 @@
+/**
+ * @file
+ * A Backbone View that provides keyboard interaction for a contextual link.
+ */
+
+(function (Drupal, Backbone) {
+
+  'use strict';
+
+  Drupal.contextual.KeyboardView = Backbone.View.extend(/** @lends Drupal.contextual.KeyboardView# */{
+
+    /**
+     * @type {object}
+     */
+    events: {
+      'focus .trigger': 'focus',
+      'focus .contextual-links a': 'focus',
+      'blur .trigger': function () { this.model.blur(); },
+      'blur .contextual-links a': function () {
+        // Set up a timeout to allow a user to tab between the trigger and the
+        // contextual links without the menu dismissing.
+        var that = this;
+        this.timer = window.setTimeout(function () {
+          that.model.close().blur();
+        }, 150);
+      }
+    },
+
+    /**
+     * Provides keyboard interaction for a contextual link.
+     *
+     * @constructs
+     *
+     * @augments Backbone.View
+     */
+    initialize: function () {
+
+      /**
+       * The timer is used to create a delay before dismissing the contextual
+       * links on blur. This is only necessary when keyboard users tab into
+       * contextual links without edit mode (i.e. without TabbingManager).
+       * That means that if we decide to disable tabbing of contextual links
+       * without edit mode, all this timer logic can go away.
+       *
+       * @type {NaN|number}
+       */
+      this.timer = NaN;
+    },
+
+    /**
+     * Sets focus on the model; Clears the timer that dismisses the links.
+     */
+    focus: function () {
+      // Clear the timeout that might have been set by blurring a link.
+      window.clearTimeout(this.timer);
+      this.model.focus();
+    }
+
+  });
+
+})(Drupal, Backbone);
diff --git a/core/modules/contextual/js/views/KeyboardView.js b/core/modules/contextual/js/views/KeyboardView.js
index 9c247730e312..1539971d6833 100644
--- a/core/modules/contextual/js/views/KeyboardView.js
+++ b/core/modules/contextual/js/views/KeyboardView.js
@@ -1,24 +1,23 @@
 /**
- * @file
- * A Backbone View that provides keyboard interaction for a contextual link.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/contextual/js/views/KeyboardView.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function (Drupal, Backbone) {
 
   'use strict';
 
-  Drupal.contextual.KeyboardView = Backbone.View.extend(/** @lends Drupal.contextual.KeyboardView# */{
-
-    /**
-     * @type {object}
-     */
+  Drupal.contextual.KeyboardView = Backbone.View.extend({
     events: {
       'focus .trigger': 'focus',
       'focus .contextual-links a': 'focus',
-      'blur .trigger': function () { this.model.blur(); },
-      'blur .contextual-links a': function () {
-        // Set up a timeout to allow a user to tab between the trigger and the
-        // contextual links without the menu dismissing.
+      'blur .trigger': function blurTrigger() {
+        this.model.blur();
+      },
+      'blur .contextual-links a': function blurContextualLinksA() {
         var that = this;
         this.timer = window.setTimeout(function () {
           that.model.close().blur();
@@ -26,36 +25,14 @@
       }
     },
 
-    /**
-     * Provides keyboard interaction for a contextual link.
-     *
-     * @constructs
-     *
-     * @augments Backbone.View
-     */
-    initialize: function () {
-
-      /**
-       * The timer is used to create a delay before dismissing the contextual
-       * links on blur. This is only necessary when keyboard users tab into
-       * contextual links without edit mode (i.e. without TabbingManager).
-       * That means that if we decide to disable tabbing of contextual links
-       * without edit mode, all this timer logic can go away.
-       *
-       * @type {NaN|number}
-       */
+    initialize: function initialize() {
       this.timer = NaN;
     },
 
-    /**
-     * Sets focus on the model; Clears the timer that dismisses the links.
-     */
-    focus: function () {
-      // Clear the timeout that might have been set by blurring a link.
+    focus: function focus() {
       window.clearTimeout(this.timer);
       this.model.focus();
     }
 
   });
-
-})(Drupal, Backbone);
+})(Drupal, Backbone);
\ No newline at end of file
diff --git a/core/modules/contextual/js/views/RegionView.es6.js b/core/modules/contextual/js/views/RegionView.es6.js
new file mode 100644
index 000000000000..e960db36bfd6
--- /dev/null
+++ b/core/modules/contextual/js/views/RegionView.es6.js
@@ -0,0 +1,57 @@
+/**
+ * @file
+ * A Backbone View that renders the visual view of a contextual region element.
+ */
+
+(function (Drupal, Backbone, Modernizr) {
+
+  'use strict';
+
+  Drupal.contextual.RegionView = Backbone.View.extend(/** @lends Drupal.contextual.RegionView# */{
+
+    /**
+     * Events for the Backbone view.
+     *
+     * @return {object}
+     *   A mapping of events to be used in the view.
+     */
+    events: function () {
+      var mapping = {
+        mouseenter: function () { this.model.set('regionIsHovered', true); },
+        mouseleave: function () {
+          this.model.close().blur().set('regionIsHovered', false);
+        }
+      };
+      // We don't want mouse hover events on touch.
+      if (Modernizr.touchevents) {
+        mapping = {};
+      }
+      return mapping;
+    },
+
+    /**
+     * Renders the visual view of a contextual region element.
+     *
+     * @constructs
+     *
+     * @augments Backbone.View
+     */
+    initialize: function () {
+      this.listenTo(this.model, 'change:hasFocus', this.render);
+    },
+
+    /**
+     * @inheritdoc
+     *
+     * @return {Drupal.contextual.RegionView}
+     *   The current contextual region view.
+     */
+    render: function () {
+      this.$el.toggleClass('focus', this.model.get('hasFocus'));
+
+      return this;
+    }
+
+  });
+
+})(Drupal, Backbone, Modernizr);
diff --git a/core/modules/contextual/js/views/RegionView.js b/core/modules/contextual/js/views/RegionView.js
index e960db36bfd6..d37e10f3c283 100644
--- a/core/modules/contextual/js/views/RegionView.js
+++ b/core/modules/contextual/js/views/RegionView.js
@@ -1,57 +1,41 @@
 /**
- * @file
- * A Backbone View that renders the visual view of a contextual region element.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/contextual/js/views/RegionView.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function (Drupal, Backbone, Modernizr) {
 
   'use strict';
 
-  Drupal.contextual.RegionView = Backbone.View.extend(/** @lends Drupal.contextual.RegionView# */{
-
-    /**
-     * Events for the Backbone view.
-     *
-     * @return {object}
-     *   A mapping of events to be used in the view.
-     */
-    events: function () {
+  Drupal.contextual.RegionView = Backbone.View.extend({
+    events: function events() {
       var mapping = {
-        mouseenter: function () { this.model.set('regionIsHovered', true); },
-        mouseleave: function () {
+        mouseenter: function mouseenter() {
+          this.model.set('regionIsHovered', true);
+        },
+        mouseleave: function mouseleave() {
           this.model.close().blur().set('regionIsHovered', false);
         }
       };
-      // We don't want mouse hover events on touch.
+
       if (Modernizr.touchevents) {
         mapping = {};
       }
       return mapping;
     },
 
-    /**
-     * Renders the visual view of a contextual region element.
-     *
-     * @constructs
-     *
-     * @augments Backbone.View
-     */
-    initialize: function () {
+    initialize: function initialize() {
       this.listenTo(this.model, 'change:hasFocus', this.render);
     },
 
-    /**
-     * @inheritdoc
-     *
-     * @return {Drupal.contextual.RegionView}
-     *   The current contextual region view.
-     */
-    render: function () {
+    render: function render() {
       this.$el.toggleClass('focus', this.model.get('hasFocus'));
 
       return this;
     }
 
   });
-
-})(Drupal, Backbone, Modernizr);
+})(Drupal, Backbone, Modernizr);
\ No newline at end of file
diff --git a/core/modules/contextual/js/views/VisualView.es6.js b/core/modules/contextual/js/views/VisualView.es6.js
new file mode 100644
index 000000000000..b22bb373dd8d
--- /dev/null
+++ b/core/modules/contextual/js/views/VisualView.es6.js
@@ -0,0 +1,80 @@
+/**
+ * @file
+ * A Backbone View that provides the visual view of a contextual link.
+ */
+
+(function (Drupal, Backbone, Modernizr) {
+
+  'use strict';
+
+  Drupal.contextual.VisualView = Backbone.View.extend(/** @lends Drupal.contextual.VisualView# */{
+
+    /**
+     * Events for the Backbone view.
+     *
+     * @return {object}
+     *   A mapping of events to be used in the view.
+     */
+    events: function () {
+      // Prevents delay and simulated mouse events.
+      var touchEndToClick = function (event) {
+        event.preventDefault();
+        event.target.click();
+      };
+      var mapping = {
+        'click .trigger': function () { this.model.toggleOpen(); },
+        'touchend .trigger': touchEndToClick,
+        'click .contextual-links a': function () { this.model.close().blur(); },
+        'touchend .contextual-links a': touchEndToClick
+      };
+      // We only want mouse hover events on non-touch.
+      if (!Modernizr.touchevents) {
+        mapping.mouseenter = function () { this.model.focus(); };
+      }
+      return mapping;
+    },
+
+    /**
+     * Renders the visual view of a contextual link. Listens to mouse & touch.
+     *
+     * @constructs
+     *
+     * @augments Backbone.View
+     */
+    initialize: function () {
+      this.listenTo(this.model, 'change', this.render);
+    },
+
+    /**
+     * @inheritdoc
+     *
+     * @return {Drupal.contextual.VisualView}
+     *   The current contextual visual view.
+     */
+    render: function () {
+      var isOpen = this.model.get('isOpen');
+      // The trigger should be visible when:
+      //  - the mouse hovered over the region,
+      //  - the trigger is locked,
+      //  - and for as long as the contextual menu is open.
+      var isVisible = this.model.get('isLocked') || this.model.get('regionIsHovered') || isOpen;
+
+      this.$el
+        // The open state determines if the links are visible.
+        .toggleClass('open', isOpen)
+        // Update the visibility of the trigger.
+        .find('.trigger').toggleClass('visually-hidden', !isVisible);
+
+      // Nested contextual region handling: hide any nested contextual triggers.
+      if ('isOpen' in this.model.changed) {
+        this.$el.closest('.contextual-region')
+          .find('.contextual .trigger:not(:first)')
+          .toggle(!isOpen);
+      }
+
+      return this;
+    }
+
+  });
+
+})(Drupal, Backbone, Modernizr);
diff --git a/core/modules/contextual/js/views/VisualView.js b/core/modules/contextual/js/views/VisualView.js
index b22bb373dd8d..c54f50f87189 100644
--- a/core/modules/contextual/js/views/VisualView.js
+++ b/core/modules/contextual/js/views/VisualView.js
@@ -1,80 +1,57 @@
 /**
- * @file
- * A Backbone View that provides the visual view of a contextual link.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/contextual/js/views/VisualView.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function (Drupal, Backbone, Modernizr) {
 
   'use strict';
 
-  Drupal.contextual.VisualView = Backbone.View.extend(/** @lends Drupal.contextual.VisualView# */{
-
-    /**
-     * Events for the Backbone view.
-     *
-     * @return {object}
-     *   A mapping of events to be used in the view.
-     */
-    events: function () {
-      // Prevents delay and simulated mouse events.
-      var touchEndToClick = function (event) {
+  Drupal.contextual.VisualView = Backbone.View.extend({
+    events: function events() {
+      var touchEndToClick = function touchEndToClick(event) {
         event.preventDefault();
         event.target.click();
       };
       var mapping = {
-        'click .trigger': function () { this.model.toggleOpen(); },
+        'click .trigger': function clickTrigger() {
+          this.model.toggleOpen();
+        },
         'touchend .trigger': touchEndToClick,
-        'click .contextual-links a': function () { this.model.close().blur(); },
+        'click .contextual-links a': function clickContextualLinksA() {
+          this.model.close().blur();
+        },
         'touchend .contextual-links a': touchEndToClick
       };
-      // We only want mouse hover events on non-touch.
+
       if (!Modernizr.touchevents) {
-        mapping.mouseenter = function () { this.model.focus(); };
+        mapping.mouseenter = function () {
+          this.model.focus();
+        };
       }
       return mapping;
     },
 
-    /**
-     * Renders the visual view of a contextual link. Listens to mouse & touch.
-     *
-     * @constructs
-     *
-     * @augments Backbone.View
-     */
-    initialize: function () {
+    initialize: function initialize() {
       this.listenTo(this.model, 'change', this.render);
     },
 
-    /**
-     * @inheritdoc
-     *
-     * @return {Drupal.contextual.VisualView}
-     *   The current contextual visual view.
-     */
-    render: function () {
+    render: function render() {
       var isOpen = this.model.get('isOpen');
-      // The trigger should be visible when:
-      //  - the mouse hovered over the region,
-      //  - the trigger is locked,
-      //  - and for as long as the contextual menu is open.
+
       var isVisible = this.model.get('isLocked') || this.model.get('regionIsHovered') || isOpen;
 
-      this.$el
-        // The open state determines if the links are visible.
-        .toggleClass('open', isOpen)
-        // Update the visibility of the trigger.
-        .find('.trigger').toggleClass('visually-hidden', !isVisible);
+      this.$el.toggleClass('open', isOpen).find('.trigger').toggleClass('visually-hidden', !isVisible);
 
-      // Nested contextual region handling: hide any nested contextual triggers.
       if ('isOpen' in this.model.changed) {
-        this.$el.closest('.contextual-region')
-          .find('.contextual .trigger:not(:first)')
-          .toggle(!isOpen);
+        this.$el.closest('.contextual-region').find('.contextual .trigger:not(:first)').toggle(!isOpen);
       }
 
       return this;
     }
 
   });
-
-})(Drupal, Backbone, Modernizr);
+})(Drupal, Backbone, Modernizr);
\ No newline at end of file
diff --git a/core/modules/editor/js/editor.admin.es6.js b/core/modules/editor/js/editor.admin.es6.js
new file mode 100644
index 000000000000..1fdb35320248
--- /dev/null
+++ b/core/modules/editor/js/editor.admin.es6.js
@@ -0,0 +1,935 @@
+/**
+ * @file
+ * Provides a JavaScript API to broadcast text editor configuration changes.
+ *
+ * Filter implementations may listen to the drupalEditorFeatureAdded,
+ * drupalEditorFeatureRemoved, and drupalEditorFeatureRemoved events on document
+ * to automatically adjust their settings based on the editor configuration.
+ */
+
+(function ($, _, Drupal, document) {
+
+  'use strict';
+
+  /**
+   * Editor configuration namespace.
+   *
+   * @namespace
+   */
+  Drupal.editorConfiguration = {
+
+    /**
+     * Must be called by a specific text editor's configuration whenever a
+     * feature is added by the user.
+     *
+     * Triggers the drupalEditorFeatureAdded event on the document, which
+     * receives a {@link Drupal.EditorFeature} object.
+     *
+     * @param {Drupal.EditorFeature} feature
+     *   A text editor feature object.
+     *
+     * @fires event:drupalEditorFeatureAdded
+     */
+    addedFeature: function (feature) {
+      $(document).trigger('drupalEditorFeatureAdded', feature);
+    },
+
+    /**
+     * Must be called by a specific text editor's configuration whenever a
+     * feature is removed by the user.
+     *
+     * Triggers the drupalEditorFeatureRemoved event on the document, which
+     * receives a {@link Drupal.EditorFeature} object.
+     *
+     * @param {Drupal.EditorFeature} feature
+     *   A text editor feature object.
+     *
+     * @fires event:drupalEditorFeatureRemoved
+     */
+    removedFeature: function (feature) {
+      $(document).trigger('drupalEditorFeatureRemoved', feature);
+    },
+
+    /**
+     * Must be called by a specific text editor's configuration whenever a
+     * feature is modified, i.e. has different rules.
+     *
+     * For example when the "Bold" button is configured to use the `<b>` tag
+     * instead of the `<strong>` tag.
+     *
+     * Triggers the drupalEditorFeatureModified event on the document, which
+     * receives a {@link Drupal.EditorFeature} object.
+     *
+     * @param {Drupal.EditorFeature} feature
+     *   A text editor feature object.
+     *
+     * @fires event:drupalEditorFeatureModified
+     */
+    modifiedFeature: function (feature) {
+      $(document).trigger('drupalEditorFeatureModified', feature);
+    },
+
+    /**
+     * May be called by a specific text editor's configuration whenever a
+     * feature is being added, to check whether it would require the filter
+     * settings to be updated.
+     *
+     * The canonical use case is when a text editor is being enabled:
+     * preferably
+     * this would not cause the filter settings to be changed; rather, the
+     * default set of buttons (features) for the text editor should adjust
+     * itself to not cause filter setting changes.
+     *
+     * Note: for filters to integrate with this functionality, it is necessary
+     * that they implement
+     * `Drupal.filterSettingsForEditors[filterID].getRules()`.
+     *
+     * @param {Drupal.EditorFeature} feature
+     *   A text editor feature object.
+     *
+     * @return {bool}
+     *   Whether the given feature is allowed by the current filters.
+     */
+    featureIsAllowedByFilters: function (feature) {
+
+      /**
+       * Generate the universe U of possible values that can result from the
+       * feature's rules' requirements.
+       *
+       * This generates an object of this form:
+       *   var universe = {
+       *     a: {
+       *       'touchedByAllowedPropertyRule': false,
+       *       'tag': false,
+       *       'attributes:href': false,
+       *       'classes:external': false,
+       *     },
+       *     strong: {
+       *       'touchedByAllowedPropertyRule': false,
+       *       'tag': false,
+       *     },
+       *     img: {
+       *       'touchedByAllowedPropertyRule': false,
+       *       'tag': false,
+       *       'attributes:src': false
+       *     }
+       *   };
+       *
+       * In this example, the given text editor feature resulted in the above
+       * universe, which shows that it must be allowed to generate the a,
+       * strong and img tags. For the a tag, it must be able to set the "href"
+       * attribute and the "external" class. For the strong tag, no further
+       * properties are required. For the img tag, the "src" attribute is
+       * required. The "tag" key is used to track whether that tag was
+       * explicitly allowed by one of the filter's rules. The
+       * "touchedByAllowedPropertyRule" key is used for state tracking that is
+       * essential for filterStatusAllowsFeature() to be able to reason: when
+       * all of a filter's rules have been applied, and none of the forbidden
+       * rules matched (which would have resulted in early termination) yet the
+       * universe has not been made empty (which would be the end result if
+       * everything in the universe were explicitly allowed), then this piece
+       * of state data enables us to determine whether a tag whose properties
+       * were not all explicitly allowed are in fact still allowed, because its
+       * tag was explicitly allowed and there were no filter rules applying
+       * "allowed tag property value" restrictions for this particular tag.
+       *
+       * @param {object} feature
+       *   The feature in question.
+       *
+       * @return {object}
+       *   The universe generated.
+       *
+       * @see findPropertyValueOnTag()
+       * @see filterStatusAllowsFeature()
+       */
+      function generateUniverseFromFeatureRequirements(feature) {
+        var properties = ['attributes', 'styles', 'classes'];
+        var universe = {};
+
+        for (var r = 0; r < feature.rules.length; r++) {
+          var featureRule = feature.rules[r];
+
+          // For each tag required by this feature rule, create a basic entry in
+          // the universe.
+          var requiredTags = featureRule.required.tags;
+          for (var t = 0; t < requiredTags.length; t++) {
+            universe[requiredTags[t]] = {
+              // Whether this tag was allowed or not.
+              tag: false,
+              // Whether any filter rule that applies to this tag had an allowed
+              // property rule. i.e. will become true if >=1 filter rule has >=1
+              // allowed property rule.
+              touchedByAllowedPropertyRule: false,
+              // Analogous, but for forbidden property rule.
+              touchedBytouchedByForbiddenPropertyRule: false
+            };
+          }
+
+          // If no required properties are defined for this rule, we can move on
+          // to the next feature.
+          if (emptyProperties(featureRule.required)) {
+            continue;
+          }
+
+          // Expand the existing universe, assume that each tags' property
+          // value is disallowed. If the filter rules allow everything in the
+          // feature's universe, then the feature is allowed.
+          for (var p = 0; p < properties.length; p++) {
+            var property = properties[p];
+            for (var pv = 0; pv < featureRule.required[property].length; pv++) {
+              var propertyValue = featureRule.required[property];
+              universe[requiredTags][property + ':' + propertyValue] = false;
+            }
+          }
+        }
+
+        return universe;
+      }
+
+      /**
+       * Provided a section of a feature or filter rule, checks if no property
+       * values are defined for all properties: attributes, classes and styles.
+       *
+       * @param {object} section
+       *   The section to check.
+       *
+       * @return {bool}
+       *   Returns true if the section has empty properties, false otherwise.
+       */
+      function emptyProperties(section) {
+        return section.attributes.length === 0 && section.classes.length === 0 && section.styles.length === 0;
+      }
+
+      /**
+       * Calls findPropertyValueOnTag on the given tag for every property value
+       * that is listed in the "propertyValues" parameter. Supports the wildcard
+       * tag.
+       *
+       * @param {object} universe
+       *   The universe to check.
+       * @param {string} tag
+       *   The tag to look for.
+       * @param {string} property
+       *   The property to check.
+       * @param {Array} propertyValues
+       *   Values of the property to check.
+       * @param {bool} allowing
+       *   Whether to update the universe or not.
+       *
+       * @return {bool}
+       *   Returns true if found, false otherwise.
+       */
+      function findPropertyValuesOnTag(universe, tag, property, propertyValues, allowing) {
+        // Detect the wildcard case.
+        if (tag === '*') {
+          return findPropertyValuesOnAllTags(universe, property, propertyValues, allowing);
+        }
+
+        var atLeastOneFound = false;
+        _.each(propertyValues, function (propertyValue) {
+          if (findPropertyValueOnTag(universe, tag, property, propertyValue, allowing)) {
+            atLeastOneFound = true;
+          }
+        });
+        return atLeastOneFound;
+      }
+
+      /**
+       * Calls findPropertyValuesOnAllTags for all tags in the universe.
+       *
+       * @param {object} universe
+       *   The universe to check.
+       * @param {string} property
+       *   The property to check.
+       * @param {Array} propertyValues
+       *   Values of the property to check.
+       * @param {bool} allowing
+       *   Whether to update the universe or not.
+       *
+       * @return {bool}
+       *   Returns true if found, false otherwise.
+       */
+      function findPropertyValuesOnAllTags(universe, property, propertyValues, allowing) {
+        var atLeastOneFound = false;
+        _.each(_.keys(universe), function (tag) {
+          if (findPropertyValuesOnTag(universe, tag, property, propertyValues, allowing)) {
+            atLeastOneFound = true;
+          }
+        });
+        return atLeastOneFound;
+      }
+
+      /**
+       * Finds out if a specific property value (potentially containing
+       * wildcards) exists on the given tag. When the "allowing" parameter
+       * equals true, the universe will be updated if that specific property
+       * value exists. Returns true if found, false otherwise.
+       *
+       * @param {object} universe
+       *   The universe to check.
+       * @param {string} tag
+       *   The tag to look for.
+       * @param {string} property
+       *   The property to check.
+       * @param {string} propertyValue
+       *   The property value to check.
+       * @param {bool} allowing
+       *   Whether to update the universe or not.
+       *
+       * @return {bool}
+       *   Returns true if found, false otherwise.
+       */
+      function findPropertyValueOnTag(universe, tag, property, propertyValue, allowing) {
+        // If the tag does not exist in the universe, then it definitely can't
+        // have this specific property value.
+        if (!_.has(universe, tag)) {
+          return false;
+        }
+
+        var key = property + ':' + propertyValue;
+
+        // Track whether a tag was touched by a filter rule that allows specific
+        // property values on this particular tag.
+        // @see generateUniverseFromFeatureRequirements
+        if (allowing) {
+          universe[tag].touchedByAllowedPropertyRule = true;
+        }
+
+        // The simple case: no wildcard in property value.
+        if (_.indexOf(propertyValue, '*') === -1) {
+          if (_.has(universe, tag) && _.has(universe[tag], key)) {
+            if (allowing) {
+              universe[tag][key] = true;
+            }
+            return true;
+          }
+          return false;
+        }
+        // The complex case: wildcard in property value.
+        else {
+          var atLeastOneFound = false;
+          var regex = key.replace(/\*/g, '[^ ]*');
+          _.each(_.keys(universe[tag]), function (key) {
+            if (key.match(regex)) {
+              atLeastOneFound = true;
+              if (allowing) {
+                universe[tag][key] = true;
+              }
+            }
+          });
+          return atLeastOneFound;
+        }
+      }
+
+      /**
+       * Deletes a tag from the universe if the tag itself and each of its
+       * properties are marked as allowed.
+       *
+       * @param {object} universe
+       *   The universe to delete from.
+       * @param {string} tag
+       *   The tag to check.
+       *
+       * @return {bool}
+       *   Whether something was deleted from the universe.
+       */
+      function deleteFromUniverseIfAllowed(universe, tag) {
+        // Detect the wildcard case.
+        if (tag === '*') {
+          return deleteAllTagsFromUniverseIfAllowed(universe);
+        }
+        if (_.has(universe, tag) && _.every(_.omit(universe[tag], 'touchedByAllowedPropertyRule'))) {
+          delete universe[tag];
+          return true;
+        }
+        return false;
+      }
+
+      /**
+       * Calls deleteFromUniverseIfAllowed for all tags in the universe.
+       *
+       * @param {object} universe
+       *   The universe to delete from.
+       *
+       * @return {bool}
+       *   Whether something was deleted from the universe.
+       */
+      function deleteAllTagsFromUniverseIfAllowed(universe) {
+        var atLeastOneDeleted = false;
+        _.each(_.keys(universe), function (tag) {
+          if (deleteFromUniverseIfAllowed(universe, tag)) {
+            atLeastOneDeleted = true;
+          }
+        });
+        return atLeastOneDeleted;
+      }
+
+      /**
+       * Checks if any filter rule forbids either a tag or a tag property value
+       * that exists in the universe.
+       *
+       * @param {object} universe
+       *   Universe to check.
+       * @param {object} filterStatus
+       *   Filter status to use for check.
+       *
+       * @return {bool}
+       *   Whether any filter rule forbids something in the universe.
+       */
+      function anyForbiddenFilterRuleMatches(universe, filterStatus) {
+        var properties = ['attributes', 'styles', 'classes'];
+
+        // Check if a tag in the universe is forbidden.
+        var allRequiredTags = _.keys(universe);
+        var filterRule;
+        for (var i = 0; i < filterStatus.rules.length; i++) {
+          filterRule = filterStatus.rules[i];
+          if (filterRule.allow === false) {
+            if (_.intersection(allRequiredTags, filterRule.tags).length > 0) {
+              return true;
+            }
+          }
+        }
+
+        // Check if a property value of a tag in the universe is forbidden.
+        // For all filter rules…
+        for (var n = 0; n < filterStatus.rules.length; n++) {
+          filterRule = filterStatus.rules[n];
+          // … if there are tags with restricted property values …
+          if (filterRule.restrictedTags.tags.length && !emptyProperties(filterRule.restrictedTags.forbidden)) {
+            // … for all those tags …
+            for (var j = 0; j < filterRule.restrictedTags.tags.length; j++) {
+              var tag = filterRule.restrictedTags.tags[j];
+              // … then iterate over all properties …
+              for (var k = 0; k < properties.length; k++) {
+                var property = properties[k];
+                // … and return true if just one of the forbidden property
+                // values for this tag and property is listed in the universe.
+                if (findPropertyValuesOnTag(universe, tag, property, filterRule.restrictedTags.forbidden[property], false)) {
+                  return true;
+                }
+              }
+            }
+          }
+        }
+
+        return false;
+      }
+
+      /**
+       * Applies every filter rule's explicit allowing of a tag or a tag
+       * property value to the universe. Whenever both the tag and all of its
+       * required property values are marked as explicitly allowed, they are
+       * deleted from the universe.
+       *
+       * @param {object} universe
+       *   Universe to delete from.
+       * @param {object} filterStatus
+       *   The filter status in question.
+       */
+      function markAllowedTagsAndPropertyValues(universe, filterStatus) {
+        var properties = ['attributes', 'styles', 'classes'];
+
+        // Check if a tag in the universe is allowed.
+        var filterRule;
+        var tag;
+        for (var l = 0; !_.isEmpty(universe) && l < filterStatus.rules.length; l++) {
+          filterRule = filterStatus.rules[l];
+          if (filterRule.allow === true) {
+            for (var m = 0; !_.isEmpty(universe) && m < filterRule.tags.length; m++) {
+              tag = filterRule.tags[m];
+              if (_.has(universe, tag)) {
+                universe[tag].tag = true;
+                deleteFromUniverseIfAllowed(universe, tag);
+              }
+            }
+          }
+        }
+
+        // Check if a property value of a tag in the universe is allowed.
+        // For all filter rules…
+        for (var i = 0; !_.isEmpty(universe) && i < filterStatus.rules.length; i++) {
+          filterRule = filterStatus.rules[i];
+          // … if there are tags with restricted property values …
+          if (filterRule.restrictedTags.tags.length && !emptyProperties(filterRule.restrictedTags.allowed)) {
+            // … for all those tags …
+            for (var j = 0; !_.isEmpty(universe) && j < filterRule.restrictedTags.tags.length; j++) {
+              tag = filterRule.restrictedTags.tags[j];
+              // … then iterate over all properties …
+              for (var k = 0; k < properties.length; k++) {
+                var property = properties[k];
+                // … and try to delete this tag from the universe if just one
+                // of the allowed property values for this tag and property is
+                // listed in the universe. (Because everything might be allowed
+                // now.)
+                if (findPropertyValuesOnTag(universe, tag, property, filterRule.restrictedTags.allowed[property], true)) {
+                  deleteFromUniverseIfAllowed(universe, tag);
+                }
+              }
+            }
+          }
+        }
+      }
+
+      /**
+       * Checks whether the current status of a filter allows a specific feature
+       * by building the universe of potential values from the feature's
+       * requirements and then checking whether anything in the filter prevents
+       * that.
+       *
+       * @param {object} filterStatus
+       *   The filter status in question.
+       * @param {object} feature
+       *   The feature requested.
+       *
+       * @return {bool}
+       *   Whether the current status of the filter allows specified feature.
+       *
+       * @see generateUniverseFromFeatureRequirements()
+       */
+      function filterStatusAllowsFeature(filterStatus, feature) {
+        // An inactive filter by definition allows the feature.
+        if (!filterStatus.active) {
+          return true;
+        }
+
+        // A feature that specifies no rules has no HTML requirements and is
+        // hence allowed by definition.
+        if (feature.rules.length === 0) {
+          return true;
+        }
+
+        // Analogously for a filter that specifies no rules.
+        if (filterStatus.rules.length === 0) {
+          return true;
+        }
+
+        // Generate the universe U of possible values that can result from the
+        // feature's rules' requirements.
+        var universe = generateUniverseFromFeatureRequirements(feature);
+
+        // If anything that is in the universe (and is thus required by the
+        // feature) is forbidden by any of the filter's rules, then this filter
+        // does not allow this feature.
+        if (anyForbiddenFilterRuleMatches(universe, filterStatus)) {
+          return false;
+        }
+
+        // Mark anything in the universe that is allowed by any of the filter's
+        // rules as allowed. If everything is explicitly allowed, then the
+        // universe will become empty.
+        markAllowedTagsAndPropertyValues(universe, filterStatus);
+
+        // If there was at least one filter rule allowing tags, then everything
+        // in the universe must be allowed for this feature to be allowed, and
+        // thus by now it must be empty. However, it is still possible that the
+        // filter allows the feature, due to no rules for allowing tag property
+        // values and/or rules for forbidding tag property values. For details:
+        // see the comments below.
+        // @see generateUniverseFromFeatureRequirements()
+        if (_.some(_.pluck(filterStatus.rules, 'allow'))) {
+          // If the universe is empty, then everything was explicitly allowed
+          // and our job is done: this filter allows this feature!
+          if (_.isEmpty(universe)) {
+            return true;
+          }
+          // Otherwise, it is still possible that this feature is allowed.
+          else {
+            // Every tag must be explicitly allowed if there are filter rules
+            // doing tag whitelisting.
+            if (!_.every(_.pluck(universe, 'tag'))) {
+              return false;
+            }
+            // Every tag was explicitly allowed, but since the universe is not
+            // empty, one or more tag properties are disallowed. However, if
+            // only blacklisting of tag properties was applied to these tags,
+            // and no whitelisting was ever applied, then it's still fine:
+            // since none of the tag properties were blacklisted, we got to
+            // this point, and since no whitelisting was applied, it doesn't
+            // matter that the properties: this could never have happened
+            // anyway. It's only this late that we can know this for certain.
+            else {
+              var tags = _.keys(universe);
+              // Figure out if there was any rule applying whitelisting tag
+              // restrictions to each of the remaining tags.
+              for (var i = 0; i < tags.length; i++) {
+                var tag = tags[i];
+                if (_.has(universe, tag)) {
+                  if (universe[tag].touchedByAllowedPropertyRule === false) {
+                    delete universe[tag];
+                  }
+                }
+              }
+              return _.isEmpty(universe);
+            }
+          }
+        }
+        // Otherwise, if all filter rules were doing blacklisting, then the sole
+        // fact that we got to this point indicates that this filter allows for
+        // everything that is required for this feature.
+        else {
+          return true;
+        }
+      }
+
+      // If any filter's current status forbids the editor feature, return
+      // false.
+      Drupal.filterConfiguration.update();
+      for (var filterID in Drupal.filterConfiguration.statuses) {
+        if (Drupal.filterConfiguration.statuses.hasOwnProperty(filterID)) {
+          var filterStatus = Drupal.filterConfiguration.statuses[filterID];
+          if (!(filterStatusAllowsFeature(filterStatus, feature))) {
+            return false;
+          }
+        }
+      }
+
+      return true;
+    }
+  };
+
+  /**
+   * Constructor for an editor feature HTML rule.
+   *
+   * Intended to be used in combination with {@link Drupal.EditorFeature}.
+   *
+   * A text editor feature rule object describes both:
+   *  - required HTML tags, attributes, styles and classes: without these, the
+   *    text editor feature is unable to function. It's possible that a
+   *  - allowed HTML tags, attributes, styles and classes: these are optional
+   *    in the strictest sense, but it is possible that the feature generates
+   *    them.
+   *
+   * The structure can be very clearly seen below: there's a "required" and an
+   * "allowed" key. For each of those, there are objects with the "tags",
+   * "attributes", "styles" and "classes" keys. For all these keys the values
+   * are initialized to the empty array. List each possible value as an array
+   * value. Besides the "required" and "allowed" keys, there's an optional
+   * "raw" key: it allows text editor implementations to optionally pass in
+   * their raw representation instead of the Drupal-defined representation for
+   * HTML rules.
+   *
+   * @example
+   * tags: ['<a>']
+   * attributes: ['href', 'alt']
+   * styles: ['color', 'text-decoration']
+   * classes: ['external', 'internal']
+   *
+   * @constructor
+   *
+   * @see Drupal.EditorFeature
+   */
+  Drupal.EditorFeatureHTMLRule = function () {
+
+    /**
+     *
+     * @type {object}
+     *
+     * @prop {Array} tags
+     * @prop {Array} attributes
+     * @prop {Array} styles
+     * @prop {Array} classes
+     */
+    this.required = {tags: [], attributes: [], styles: [], classes: []};
+
+    /**
+     *
+     * @type {object}
+     *
+     * @prop {Array} tags
+     * @prop {Array} attributes
+     * @prop {Array} styles
+     * @prop {Array} classes
+     */
+    this.allowed = {tags: [], attributes: [], styles: [], classes: []};
+
+    /**
+     *
+     * @type {null}
+     */
+    this.raw = null;
+  };
+
+  /**
+   * A text editor feature object. Initialized with the feature name.
+   *
+   * Contains a set of HTML rules ({@link Drupal.EditorFeatureHTMLRule} objects)
+   * that describe which HTML tags, attributes, styles and classes are required
+   * (i.e. essential for the feature to function at all) and which are allowed
+   * (i.e. the feature may generate this, but they're not essential).
+   *
+   * It is necessary to allow for multiple HTML rules per feature: with just
+   * one HTML rule per feature, there is not enough expressiveness to describe
+   * certain cases. For example: a "table" feature would probably require the
+   * `<table>` tag, and might allow e.g. the "summary" attribute on that tag.
+   * However, the table feature would also require the `<tr>` and `<td>` tags,
+   * but it doesn't make sense to allow for a "summary" attribute on these tags.
+   * Hence these would need to be split in two separate rules.
+   *
+   * HTML rules must be added with the `addHTMLRule()` method. A feature that
+   * has zero HTML rules does not create or modify HTML.
+   *
+   * @constructor
+   *
+   * @param {string} name
+   *   The name of the feature.
+   *
+   * @see Drupal.EditorFeatureHTMLRule
+   */
+  Drupal.EditorFeature = function (name) {
+    this.name = name;
+    this.rules = [];
+  };
+
+  /**
+   * Adds a HTML rule to the list of HTML rules for this feature.
+   *
+   * @param {Drupal.EditorFeatureHTMLRule} rule
+   *   A text editor feature HTML rule.
+   */
+  Drupal.EditorFeature.prototype.addHTMLRule = function (rule) {
+    this.rules.push(rule);
+  };
+
+  /**
+   * Text filter status object. Initialized with the filter ID.
+   *
+   * Indicates whether the text filter is currently active (enabled) or not.
+   *
+   * Contains a set of HTML rules ({@link Drupal.FilterHTMLRule} objects) that
+   * describe which HTML tags are allowed or forbidden. They can also describe
+   * for a set of tags (or all tags) which attributes, styles and classes are
+   * allowed and which are forbidden.
+   *
+   * It is necessary to allow for multiple HTML rules per feature, for
+   * analogous reasons as {@link Drupal.EditorFeature}.
+   *
+   * HTML rules must be added with the `addHTMLRule()` method. A filter that has
+   * zero HTML rules does not disallow any HTML.
+   *
+   * @constructor
+   *
+   * @param {string} name
+   *   The name of the feature.
+   *
+   * @see Drupal.FilterHTMLRule
+   */
+  Drupal.FilterStatus = function (name) {
+
+    /**
+     *
+     * @type {string}
+     */
+    this.name = name;
+
+    /**
+     *
+     * @type {bool}
+     */
+    this.active = false;
+
+    /**
+     *
+     * @type {Array.<Drupal.FilterHTMLRule>}
+     */
+    this.rules = [];
+  };
+
+  /**
+   * Adds a HTML rule to the list of HTML rules for this filter.
+   *
+   * @param {Drupal.FilterHTMLRule} rule
+   *   A text filter HTML rule.
+   */
+  Drupal.FilterStatus.prototype.addHTMLRule = function (rule) {
+    this.rules.push(rule);
+  };
+
+  /**
+   * A text filter HTML rule object.
+   *
+   * Intended to be used in combination with {@link Drupal.FilterStatus}.
+   *
+   * A text filter rule object describes:
+   *  1. allowed or forbidden tags: (optional) whitelist or blacklist HTML tags
+   *  2. restricted tag properties: (optional) whitelist or blacklist
+   *     attributes, styles and classes on a set of HTML tags.
+   *
+   * Typically, each text filter rule object does either 1 or 2, not both.
+   *
+   * The structure can be very clearly seen below:
+   *  1. use the "tags" key to list HTML tags, and set the "allow" key to
+   *     either true (to allow these HTML tags) or false (to forbid these HTML
+   *     tags). If you leave the "tags" key's default value (the empty array),
+   *     no restrictions are applied.
+   *  2. all nested within the "restrictedTags" key: use the "tags" subkey to
+   *     list HTML tags to which you want to apply property restrictions, then
+   *     use the "allowed" subkey to whitelist specific property values, and
+   *     similarly use the "forbidden" subkey to blacklist specific property
+   *     values.
+   *
+   * @example
+   * <caption>Whitelist the "p", "strong" and "a" HTML tags.</caption>
+   * {
+   *   tags: ['p', 'strong', 'a'],
+   *   allow: true,
+   *   restrictedTags: {
+   *     tags: [],
+   *     allowed: { attributes: [], styles: [], classes: [] },
+   *     forbidden: { attributes: [], styles: [], classes: [] }
+   *   }
+   * }
+   * @example
+   * <caption>For the "a" HTML tag, only allow the "href" attribute
+   * and the "external" class and disallow the "target" attribute.</caption>
+   * {
+   *   tags: [],
+   *   allow: null,
+   *   restrictedTags: {
+   *     tags: ['a'],
+   *     allowed: { attributes: ['href'], styles: [], classes: ['external'] },
+   *     forbidden: { attributes: ['target'], styles: [], classes: [] }
+   *   }
+   * }
+   * @example
+   * <caption>For all tags, allow the "data-*" attribute (that is, any
+   * attribute that begins with "data-").</caption>
+   * {
+   *   tags: [],
+   *   allow: null,
+   *   restrictedTags: {
+   *     tags: ['*'],
+   *     allowed: { attributes: ['data-*'], styles: [], classes: [] },
+   *     forbidden: { attributes: [], styles: [], classes: [] }
+   *   }
+   * }
+   *
+   * @return {object}
+   *   An object with the following structure:
+   * ```
+   * {
+   *   tags: Array,
+   *   allow: null,
+   *   restrictedTags: {
+   *     tags: Array,
+   *     allowed: {attributes: Array, styles: Array, classes: Array},
+   *     forbidden: {attributes: Array, styles: Array, classes: Array}
+   *   }
+   * }
+   * ```
+   *
+   * @see Drupal.FilterStatus
+   */
+  Drupal.FilterHTMLRule = function () {
+    // Allow or forbid tags.
+    this.tags = [];
+    this.allow = null;
+
+    // Apply restrictions to properties set on tags.
+    this.restrictedTags = {
+      tags: [],
+      allowed: {attributes: [], styles: [], classes: []},
+      forbidden: {attributes: [], styles: [], classes: []}
+    };
+
+    return this;
+  };
+
+  Drupal.FilterHTMLRule.prototype.clone = function () {
+    var clone = new Drupal.FilterHTMLRule();
+    clone.tags = this.tags.slice(0);
+    clone.allow = this.allow;
+    clone.restrictedTags.tags = this.restrictedTags.tags.slice(0);
+    clone.restrictedTags.allowed.attributes = this.restrictedTags.allowed.attributes.slice(0);
+    clone.restrictedTags.allowed.styles = this.restrictedTags.allowed.styles.slice(0);
+    clone.restrictedTags.allowed.classes = this.restrictedTags.allowed.classes.slice(0);
+    clone.restrictedTags.forbidden.attributes = this.restrictedTags.forbidden.attributes.slice(0);
+    clone.restrictedTags.forbidden.styles = this.restrictedTags.forbidden.styles.slice(0);
+    clone.restrictedTags.forbidden.classes = this.restrictedTags.forbidden.classes.slice(0);
+    return clone;
+  };
+
+  /**
+   * Tracks the configuration of all text filters in {@link Drupal.FilterStatus}
+   * objects for {@link Drupal.editorConfiguration.featureIsAllowedByFilters}.
+   *
+   * @namespace
+   */
+  Drupal.filterConfiguration = {
+
+    /**
+     * Drupal.FilterStatus objects, keyed by filter ID.
+     *
+     * @type {Object.<string, Drupal.FilterStatus>}
+     */
+    statuses: {},
+
+    /**
+     * Live filter setting parsers.
+     *
+     * Object keyed by filter ID, for those filters that implement it.
+     *
+     * Filters should load the implementing JavaScript on the filter
+     * configuration form and implement
+     * `Drupal.filterSettings[filterID].getRules()`, which should return an
+     * array of {@link Drupal.FilterHTMLRule} objects.
+     *
+     * @namespace
+     */
+    liveSettingParsers: {},
+
+    /**
+     * Updates all {@link Drupal.FilterStatus} objects to reflect current state.
+     *
+     * Automatically checks whether a filter is currently enabled or not. To
+     * support more finegrained.
+     *
+     * If a filter implements a live setting parser, then that will be used to
+     * keep the HTML rules for the {@link Drupal.FilterStatus} object
+     * up-to-date.
+     */
+    update: function () {
+      for (var filterID in Drupal.filterConfiguration.statuses) {
+        if (Drupal.filterConfiguration.statuses.hasOwnProperty(filterID)) {
+          // Update status.
+          Drupal.filterConfiguration.statuses[filterID].active = $('[name="filters[' + filterID + '][status]"]').is(':checked');
+
+          // Update current rules.
+          if (Drupal.filterConfiguration.liveSettingParsers[filterID]) {
+            Drupal.filterConfiguration.statuses[filterID].rules = Drupal.filterConfiguration.liveSettingParsers[filterID].getRules();
+          }
+        }
+      }
+    }
+
+  };
+
+  /**
+   * Initializes {@link Drupal.filterConfiguration}.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Gets filter configuration from filter form input.
+   */
+  Drupal.behaviors.initializeFilterConfiguration = {
+    attach: function (context, settings) {
+      var $context = $(context);
+
+      $context.find('#filters-status-wrapper input.form-checkbox').once('filter-editor-status').each(function () {
+        var $checkbox = $(this);
+        var nameAttribute = $checkbox.attr('name');
+
+        // The filter's checkbox has a name attribute of the form
+        // "filters[<name of filter>][status]", parse "<name of filter>"
+        // from it.
+        var filterID = nameAttribute.substring(8, nameAttribute.indexOf(']'));
+
+        // Create a Drupal.FilterStatus object to track the state (whether it's
+        // active or not and its current settings, if any) of each filter.
+        Drupal.filterConfiguration.statuses[filterID] = new Drupal.FilterStatus(filterID);
+      });
+    }
+  };
+
+})(jQuery, _, Drupal, document);
diff --git a/core/modules/editor/js/editor.admin.js b/core/modules/editor/js/editor.admin.js
index 1fdb35320248..d2ea26bd9e2e 100644
--- a/core/modules/editor/js/editor.admin.js
+++ b/core/modules/editor/js/editor.admin.js
@@ -1,147 +1,29 @@
 /**
- * @file
- * Provides a JavaScript API to broadcast text editor configuration changes.
- *
- * Filter implementations may listen to the drupalEditorFeatureAdded,
- * drupalEditorFeatureRemoved, and drupalEditorFeatureRemoved events on document
- * to automatically adjust their settings based on the editor configuration.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/editor/js/editor.admin.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, _, Drupal, document) {
 
   'use strict';
 
-  /**
-   * Editor configuration namespace.
-   *
-   * @namespace
-   */
   Drupal.editorConfiguration = {
-
-    /**
-     * Must be called by a specific text editor's configuration whenever a
-     * feature is added by the user.
-     *
-     * Triggers the drupalEditorFeatureAdded event on the document, which
-     * receives a {@link Drupal.EditorFeature} object.
-     *
-     * @param {Drupal.EditorFeature} feature
-     *   A text editor feature object.
-     *
-     * @fires event:drupalEditorFeatureAdded
-     */
-    addedFeature: function (feature) {
+    addedFeature: function addedFeature(feature) {
       $(document).trigger('drupalEditorFeatureAdded', feature);
     },
 
-    /**
-     * Must be called by a specific text editor's configuration whenever a
-     * feature is removed by the user.
-     *
-     * Triggers the drupalEditorFeatureRemoved event on the document, which
-     * receives a {@link Drupal.EditorFeature} object.
-     *
-     * @param {Drupal.EditorFeature} feature
-     *   A text editor feature object.
-     *
-     * @fires event:drupalEditorFeatureRemoved
-     */
-    removedFeature: function (feature) {
+    removedFeature: function removedFeature(feature) {
       $(document).trigger('drupalEditorFeatureRemoved', feature);
     },
 
-    /**
-     * Must be called by a specific text editor's configuration whenever a
-     * feature is modified, i.e. has different rules.
-     *
-     * For example when the "Bold" button is configured to use the `<b>` tag
-     * instead of the `<strong>` tag.
-     *
-     * Triggers the drupalEditorFeatureModified event on the document, which
-     * receives a {@link Drupal.EditorFeature} object.
-     *
-     * @param {Drupal.EditorFeature} feature
-     *   A text editor feature object.
-     *
-     * @fires event:drupalEditorFeatureModified
-     */
-    modifiedFeature: function (feature) {
+    modifiedFeature: function modifiedFeature(feature) {
       $(document).trigger('drupalEditorFeatureModified', feature);
     },
 
-    /**
-     * May be called by a specific text editor's configuration whenever a
-     * feature is being added, to check whether it would require the filter
-     * settings to be updated.
-     *
-     * The canonical use case is when a text editor is being enabled:
-     * preferably
-     * this would not cause the filter settings to be changed; rather, the
-     * default set of buttons (features) for the text editor should adjust
-     * itself to not cause filter setting changes.
-     *
-     * Note: for filters to integrate with this functionality, it is necessary
-     * that they implement
-     * `Drupal.filterSettingsForEditors[filterID].getRules()`.
-     *
-     * @param {Drupal.EditorFeature} feature
-     *   A text editor feature object.
-     *
-     * @return {bool}
-     *   Whether the given feature is allowed by the current filters.
-     */
-    featureIsAllowedByFilters: function (feature) {
-
-      /**
-       * Generate the universe U of possible values that can result from the
-       * feature's rules' requirements.
-       *
-       * This generates an object of this form:
-       *   var universe = {
-       *     a: {
-       *       'touchedByAllowedPropertyRule': false,
-       *       'tag': false,
-       *       'attributes:href': false,
-       *       'classes:external': false,
-       *     },
-       *     strong: {
-       *       'touchedByAllowedPropertyRule': false,
-       *       'tag': false,
-       *     },
-       *     img: {
-       *       'touchedByAllowedPropertyRule': false,
-       *       'tag': false,
-       *       'attributes:src': false
-       *     }
-       *   };
-       *
-       * In this example, the given text editor feature resulted in the above
-       * universe, which shows that it must be allowed to generate the a,
-       * strong and img tags. For the a tag, it must be able to set the "href"
-       * attribute and the "external" class. For the strong tag, no further
-       * properties are required. For the img tag, the "src" attribute is
-       * required. The "tag" key is used to track whether that tag was
-       * explicitly allowed by one of the filter's rules. The
-       * "touchedByAllowedPropertyRule" key is used for state tracking that is
-       * essential for filterStatusAllowsFeature() to be able to reason: when
-       * all of a filter's rules have been applied, and none of the forbidden
-       * rules matched (which would have resulted in early termination) yet the
-       * universe has not been made empty (which would be the end result if
-       * everything in the universe were explicitly allowed), then this piece
-       * of state data enables us to determine whether a tag whose properties
-       * were not all explicitly allowed are in fact still allowed, because its
-       * tag was explicitly allowed and there were no filter rules applying
-       * "allowed tag property value" restrictions for this particular tag.
-       *
-       * @param {object} feature
-       *   The feature in question.
-       *
-       * @return {object}
-       *   The universe generated.
-       *
-       * @see findPropertyValueOnTag()
-       * @see filterStatusAllowsFeature()
-       */
+    featureIsAllowedByFilters: function featureIsAllowedByFilters(feature) {
       function generateUniverseFromFeatureRequirements(feature) {
         var properties = ['attributes', 'styles', 'classes'];
         var universe = {};
@@ -149,31 +31,21 @@
         for (var r = 0; r < feature.rules.length; r++) {
           var featureRule = feature.rules[r];
 
-          // For each tag required by this feature rule, create a basic entry in
-          // the universe.
           var requiredTags = featureRule.required.tags;
           for (var t = 0; t < requiredTags.length; t++) {
             universe[requiredTags[t]] = {
-              // Whether this tag was allowed or not.
               tag: false,
-              // Whether any filter rule that applies to this tag had an allowed
-              // property rule. i.e. will become true if >=1 filter rule has >=1
-              // allowed property rule.
+
               touchedByAllowedPropertyRule: false,
-              // Analogous, but for forbidden property rule.
+
               touchedBytouchedByForbiddenPropertyRule: false
             };
           }
 
-          // If no required properties are defined for this rule, we can move on
-          // to the next feature.
           if (emptyProperties(featureRule.required)) {
             continue;
           }
 
-          // Expand the existing universe, assume that each tags' property
-          // value is disallowed. If the filter rules allow everything in the
-          // feature's universe, then the feature is allowed.
           for (var p = 0; p < properties.length; p++) {
             var property = properties[p];
             for (var pv = 0; pv < featureRule.required[property].length; pv++) {
@@ -186,41 +58,11 @@
         return universe;
       }
 
-      /**
-       * Provided a section of a feature or filter rule, checks if no property
-       * values are defined for all properties: attributes, classes and styles.
-       *
-       * @param {object} section
-       *   The section to check.
-       *
-       * @return {bool}
-       *   Returns true if the section has empty properties, false otherwise.
-       */
       function emptyProperties(section) {
         return section.attributes.length === 0 && section.classes.length === 0 && section.styles.length === 0;
       }
 
-      /**
-       * Calls findPropertyValueOnTag on the given tag for every property value
-       * that is listed in the "propertyValues" parameter. Supports the wildcard
-       * tag.
-       *
-       * @param {object} universe
-       *   The universe to check.
-       * @param {string} tag
-       *   The tag to look for.
-       * @param {string} property
-       *   The property to check.
-       * @param {Array} propertyValues
-       *   Values of the property to check.
-       * @param {bool} allowing
-       *   Whether to update the universe or not.
-       *
-       * @return {bool}
-       *   Returns true if found, false otherwise.
-       */
       function findPropertyValuesOnTag(universe, tag, property, propertyValues, allowing) {
-        // Detect the wildcard case.
         if (tag === '*') {
           return findPropertyValuesOnAllTags(universe, property, propertyValues, allowing);
         }
@@ -234,21 +76,6 @@
         return atLeastOneFound;
       }
 
-      /**
-       * Calls findPropertyValuesOnAllTags for all tags in the universe.
-       *
-       * @param {object} universe
-       *   The universe to check.
-       * @param {string} property
-       *   The property to check.
-       * @param {Array} propertyValues
-       *   Values of the property to check.
-       * @param {bool} allowing
-       *   Whether to update the universe or not.
-       *
-       * @return {bool}
-       *   Returns true if found, false otherwise.
-       */
       function findPropertyValuesOnAllTags(universe, property, propertyValues, allowing) {
         var atLeastOneFound = false;
         _.each(_.keys(universe), function (tag) {
@@ -259,43 +86,17 @@
         return atLeastOneFound;
       }
 
-      /**
-       * Finds out if a specific property value (potentially containing
-       * wildcards) exists on the given tag. When the "allowing" parameter
-       * equals true, the universe will be updated if that specific property
-       * value exists. Returns true if found, false otherwise.
-       *
-       * @param {object} universe
-       *   The universe to check.
-       * @param {string} tag
-       *   The tag to look for.
-       * @param {string} property
-       *   The property to check.
-       * @param {string} propertyValue
-       *   The property value to check.
-       * @param {bool} allowing
-       *   Whether to update the universe or not.
-       *
-       * @return {bool}
-       *   Returns true if found, false otherwise.
-       */
       function findPropertyValueOnTag(universe, tag, property, propertyValue, allowing) {
-        // If the tag does not exist in the universe, then it definitely can't
-        // have this specific property value.
         if (!_.has(universe, tag)) {
           return false;
         }
 
         var key = property + ':' + propertyValue;
 
-        // Track whether a tag was touched by a filter rule that allows specific
-        // property values on this particular tag.
-        // @see generateUniverseFromFeatureRequirements
         if (allowing) {
           universe[tag].touchedByAllowedPropertyRule = true;
         }
 
-        // The simple case: no wildcard in property value.
         if (_.indexOf(propertyValue, '*') === -1) {
           if (_.has(universe, tag) && _.has(universe[tag], key)) {
             if (allowing) {
@@ -304,37 +105,22 @@
             return true;
           }
           return false;
-        }
-        // The complex case: wildcard in property value.
-        else {
-          var atLeastOneFound = false;
-          var regex = key.replace(/\*/g, '[^ ]*');
-          _.each(_.keys(universe[tag]), function (key) {
-            if (key.match(regex)) {
-              atLeastOneFound = true;
-              if (allowing) {
-                universe[tag][key] = true;
+        } else {
+            var atLeastOneFound = false;
+            var regex = key.replace(/\*/g, '[^ ]*');
+            _.each(_.keys(universe[tag]), function (key) {
+              if (key.match(regex)) {
+                atLeastOneFound = true;
+                if (allowing) {
+                  universe[tag][key] = true;
+                }
               }
-            }
-          });
-          return atLeastOneFound;
-        }
+            });
+            return atLeastOneFound;
+          }
       }
 
-      /**
-       * Deletes a tag from the universe if the tag itself and each of its
-       * properties are marked as allowed.
-       *
-       * @param {object} universe
-       *   The universe to delete from.
-       * @param {string} tag
-       *   The tag to check.
-       *
-       * @return {bool}
-       *   Whether something was deleted from the universe.
-       */
       function deleteFromUniverseIfAllowed(universe, tag) {
-        // Detect the wildcard case.
         if (tag === '*') {
           return deleteAllTagsFromUniverseIfAllowed(universe);
         }
@@ -345,15 +131,6 @@
         return false;
       }
 
-      /**
-       * Calls deleteFromUniverseIfAllowed for all tags in the universe.
-       *
-       * @param {object} universe
-       *   The universe to delete from.
-       *
-       * @return {bool}
-       *   Whether something was deleted from the universe.
-       */
       function deleteAllTagsFromUniverseIfAllowed(universe) {
         var atLeastOneDeleted = false;
         _.each(_.keys(universe), function (tag) {
@@ -364,22 +141,9 @@
         return atLeastOneDeleted;
       }
 
-      /**
-       * Checks if any filter rule forbids either a tag or a tag property value
-       * that exists in the universe.
-       *
-       * @param {object} universe
-       *   Universe to check.
-       * @param {object} filterStatus
-       *   Filter status to use for check.
-       *
-       * @return {bool}
-       *   Whether any filter rule forbids something in the universe.
-       */
       function anyForbiddenFilterRuleMatches(universe, filterStatus) {
         var properties = ['attributes', 'styles', 'classes'];
 
-        // Check if a tag in the universe is forbidden.
         var allRequiredTags = _.keys(universe);
         var filterRule;
         for (var i = 0; i < filterStatus.rules.length; i++) {
@@ -391,20 +155,16 @@
           }
         }
 
-        // Check if a property value of a tag in the universe is forbidden.
-        // For all filter rules…
         for (var n = 0; n < filterStatus.rules.length; n++) {
           filterRule = filterStatus.rules[n];
-          // … if there are tags with restricted property values …
+
           if (filterRule.restrictedTags.tags.length && !emptyProperties(filterRule.restrictedTags.forbidden)) {
-            // … for all those tags …
             for (var j = 0; j < filterRule.restrictedTags.tags.length; j++) {
               var tag = filterRule.restrictedTags.tags[j];
-              // … then iterate over all properties …
+
               for (var k = 0; k < properties.length; k++) {
                 var property = properties[k];
-                // … and return true if just one of the forbidden property
-                // values for this tag and property is listed in the universe.
+
                 if (findPropertyValuesOnTag(universe, tag, property, filterRule.restrictedTags.forbidden[property], false)) {
                   return true;
                 }
@@ -416,21 +176,9 @@
         return false;
       }
 
-      /**
-       * Applies every filter rule's explicit allowing of a tag or a tag
-       * property value to the universe. Whenever both the tag and all of its
-       * required property values are marked as explicitly allowed, they are
-       * deleted from the universe.
-       *
-       * @param {object} universe
-       *   Universe to delete from.
-       * @param {object} filterStatus
-       *   The filter status in question.
-       */
       function markAllowedTagsAndPropertyValues(universe, filterStatus) {
         var properties = ['attributes', 'styles', 'classes'];
 
-        // Check if a tag in the universe is allowed.
         var filterRule;
         var tag;
         for (var l = 0; !_.isEmpty(universe) && l < filterStatus.rules.length; l++) {
@@ -446,22 +194,16 @@
           }
         }
 
-        // Check if a property value of a tag in the universe is allowed.
-        // For all filter rules…
         for (var i = 0; !_.isEmpty(universe) && i < filterStatus.rules.length; i++) {
           filterRule = filterStatus.rules[i];
-          // … if there are tags with restricted property values …
+
           if (filterRule.restrictedTags.tags.length && !emptyProperties(filterRule.restrictedTags.allowed)) {
-            // … for all those tags …
             for (var j = 0; !_.isEmpty(universe) && j < filterRule.restrictedTags.tags.length; j++) {
               tag = filterRule.restrictedTags.tags[j];
-              // … then iterate over all properties …
+
               for (var k = 0; k < properties.length; k++) {
                 var property = properties[k];
-                // … and try to delete this tag from the universe if just one
-                // of the allowed property values for this tag and property is
-                // listed in the universe. (Because everything might be allowed
-                // now.)
+
                 if (findPropertyValuesOnTag(universe, tag, property, filterRule.restrictedTags.allowed[property], true)) {
                   deleteFromUniverseIfAllowed(universe, tag);
                 }
@@ -471,114 +213,57 @@
         }
       }
 
-      /**
-       * Checks whether the current status of a filter allows a specific feature
-       * by building the universe of potential values from the feature's
-       * requirements and then checking whether anything in the filter prevents
-       * that.
-       *
-       * @param {object} filterStatus
-       *   The filter status in question.
-       * @param {object} feature
-       *   The feature requested.
-       *
-       * @return {bool}
-       *   Whether the current status of the filter allows specified feature.
-       *
-       * @see generateUniverseFromFeatureRequirements()
-       */
       function filterStatusAllowsFeature(filterStatus, feature) {
-        // An inactive filter by definition allows the feature.
         if (!filterStatus.active) {
           return true;
         }
 
-        // A feature that specifies no rules has no HTML requirements and is
-        // hence allowed by definition.
         if (feature.rules.length === 0) {
           return true;
         }
 
-        // Analogously for a filter that specifies no rules.
         if (filterStatus.rules.length === 0) {
           return true;
         }
 
-        // Generate the universe U of possible values that can result from the
-        // feature's rules' requirements.
         var universe = generateUniverseFromFeatureRequirements(feature);
 
-        // If anything that is in the universe (and is thus required by the
-        // feature) is forbidden by any of the filter's rules, then this filter
-        // does not allow this feature.
         if (anyForbiddenFilterRuleMatches(universe, filterStatus)) {
           return false;
         }
 
-        // Mark anything in the universe that is allowed by any of the filter's
-        // rules as allowed. If everything is explicitly allowed, then the
-        // universe will become empty.
         markAllowedTagsAndPropertyValues(universe, filterStatus);
 
-        // If there was at least one filter rule allowing tags, then everything
-        // in the universe must be allowed for this feature to be allowed, and
-        // thus by now it must be empty. However, it is still possible that the
-        // filter allows the feature, due to no rules for allowing tag property
-        // values and/or rules for forbidding tag property values. For details:
-        // see the comments below.
-        // @see generateUniverseFromFeatureRequirements()
         if (_.some(_.pluck(filterStatus.rules, 'allow'))) {
-          // If the universe is empty, then everything was explicitly allowed
-          // and our job is done: this filter allows this feature!
           if (_.isEmpty(universe)) {
             return true;
-          }
-          // Otherwise, it is still possible that this feature is allowed.
-          else {
-            // Every tag must be explicitly allowed if there are filter rules
-            // doing tag whitelisting.
-            if (!_.every(_.pluck(universe, 'tag'))) {
-              return false;
-            }
-            // Every tag was explicitly allowed, but since the universe is not
-            // empty, one or more tag properties are disallowed. However, if
-            // only blacklisting of tag properties was applied to these tags,
-            // and no whitelisting was ever applied, then it's still fine:
-            // since none of the tag properties were blacklisted, we got to
-            // this point, and since no whitelisting was applied, it doesn't
-            // matter that the properties: this could never have happened
-            // anyway. It's only this late that we can know this for certain.
-            else {
-              var tags = _.keys(universe);
-              // Figure out if there was any rule applying whitelisting tag
-              // restrictions to each of the remaining tags.
-              for (var i = 0; i < tags.length; i++) {
-                var tag = tags[i];
-                if (_.has(universe, tag)) {
-                  if (universe[tag].touchedByAllowedPropertyRule === false) {
-                    delete universe[tag];
+          } else {
+              if (!_.every(_.pluck(universe, 'tag'))) {
+                return false;
+              } else {
+                  var tags = _.keys(universe);
+
+                  for (var i = 0; i < tags.length; i++) {
+                    var tag = tags[i];
+                    if (_.has(universe, tag)) {
+                      if (universe[tag].touchedByAllowedPropertyRule === false) {
+                        delete universe[tag];
+                      }
+                    }
                   }
+                  return _.isEmpty(universe);
                 }
-              }
-              return _.isEmpty(universe);
             }
+        } else {
+            return true;
           }
-        }
-        // Otherwise, if all filter rules were doing blacklisting, then the sole
-        // fact that we got to this point indicates that this filter allows for
-        // everything that is required for this feature.
-        else {
-          return true;
-        }
       }
 
-      // If any filter's current status forbids the editor feature, return
-      // false.
       Drupal.filterConfiguration.update();
       for (var filterID in Drupal.filterConfiguration.statuses) {
         if (Drupal.filterConfiguration.statuses.hasOwnProperty(filterID)) {
           var filterStatus = Drupal.filterConfiguration.statuses[filterID];
-          if (!(filterStatusAllowsFeature(filterStatus, feature))) {
+          if (!filterStatusAllowsFeature(filterStatus, feature)) {
             return false;
           }
         }
@@ -588,248 +273,43 @@
     }
   };
 
-  /**
-   * Constructor for an editor feature HTML rule.
-   *
-   * Intended to be used in combination with {@link Drupal.EditorFeature}.
-   *
-   * A text editor feature rule object describes both:
-   *  - required HTML tags, attributes, styles and classes: without these, the
-   *    text editor feature is unable to function. It's possible that a
-   *  - allowed HTML tags, attributes, styles and classes: these are optional
-   *    in the strictest sense, but it is possible that the feature generates
-   *    them.
-   *
-   * The structure can be very clearly seen below: there's a "required" and an
-   * "allowed" key. For each of those, there are objects with the "tags",
-   * "attributes", "styles" and "classes" keys. For all these keys the values
-   * are initialized to the empty array. List each possible value as an array
-   * value. Besides the "required" and "allowed" keys, there's an optional
-   * "raw" key: it allows text editor implementations to optionally pass in
-   * their raw representation instead of the Drupal-defined representation for
-   * HTML rules.
-   *
-   * @example
-   * tags: ['<a>']
-   * attributes: ['href', 'alt']
-   * styles: ['color', 'text-decoration']
-   * classes: ['external', 'internal']
-   *
-   * @constructor
-   *
-   * @see Drupal.EditorFeature
-   */
   Drupal.EditorFeatureHTMLRule = function () {
+    this.required = { tags: [], attributes: [], styles: [], classes: [] };
+
+    this.allowed = { tags: [], attributes: [], styles: [], classes: [] };
 
-    /**
-     *
-     * @type {object}
-     *
-     * @prop {Array} tags
-     * @prop {Array} attributes
-     * @prop {Array} styles
-     * @prop {Array} classes
-     */
-    this.required = {tags: [], attributes: [], styles: [], classes: []};
-
-    /**
-     *
-     * @type {object}
-     *
-     * @prop {Array} tags
-     * @prop {Array} attributes
-     * @prop {Array} styles
-     * @prop {Array} classes
-     */
-    this.allowed = {tags: [], attributes: [], styles: [], classes: []};
-
-    /**
-     *
-     * @type {null}
-     */
     this.raw = null;
   };
 
-  /**
-   * A text editor feature object. Initialized with the feature name.
-   *
-   * Contains a set of HTML rules ({@link Drupal.EditorFeatureHTMLRule} objects)
-   * that describe which HTML tags, attributes, styles and classes are required
-   * (i.e. essential for the feature to function at all) and which are allowed
-   * (i.e. the feature may generate this, but they're not essential).
-   *
-   * It is necessary to allow for multiple HTML rules per feature: with just
-   * one HTML rule per feature, there is not enough expressiveness to describe
-   * certain cases. For example: a "table" feature would probably require the
-   * `<table>` tag, and might allow e.g. the "summary" attribute on that tag.
-   * However, the table feature would also require the `<tr>` and `<td>` tags,
-   * but it doesn't make sense to allow for a "summary" attribute on these tags.
-   * Hence these would need to be split in two separate rules.
-   *
-   * HTML rules must be added with the `addHTMLRule()` method. A feature that
-   * has zero HTML rules does not create or modify HTML.
-   *
-   * @constructor
-   *
-   * @param {string} name
-   *   The name of the feature.
-   *
-   * @see Drupal.EditorFeatureHTMLRule
-   */
   Drupal.EditorFeature = function (name) {
     this.name = name;
     this.rules = [];
   };
 
-  /**
-   * Adds a HTML rule to the list of HTML rules for this feature.
-   *
-   * @param {Drupal.EditorFeatureHTMLRule} rule
-   *   A text editor feature HTML rule.
-   */
   Drupal.EditorFeature.prototype.addHTMLRule = function (rule) {
     this.rules.push(rule);
   };
 
-  /**
-   * Text filter status object. Initialized with the filter ID.
-   *
-   * Indicates whether the text filter is currently active (enabled) or not.
-   *
-   * Contains a set of HTML rules ({@link Drupal.FilterHTMLRule} objects) that
-   * describe which HTML tags are allowed or forbidden. They can also describe
-   * for a set of tags (or all tags) which attributes, styles and classes are
-   * allowed and which are forbidden.
-   *
-   * It is necessary to allow for multiple HTML rules per feature, for
-   * analogous reasons as {@link Drupal.EditorFeature}.
-   *
-   * HTML rules must be added with the `addHTMLRule()` method. A filter that has
-   * zero HTML rules does not disallow any HTML.
-   *
-   * @constructor
-   *
-   * @param {string} name
-   *   The name of the feature.
-   *
-   * @see Drupal.FilterHTMLRule
-   */
   Drupal.FilterStatus = function (name) {
-
-    /**
-     *
-     * @type {string}
-     */
     this.name = name;
 
-    /**
-     *
-     * @type {bool}
-     */
     this.active = false;
 
-    /**
-     *
-     * @type {Array.<Drupal.FilterHTMLRule>}
-     */
     this.rules = [];
   };
 
-  /**
-   * Adds a HTML rule to the list of HTML rules for this filter.
-   *
-   * @param {Drupal.FilterHTMLRule} rule
-   *   A text filter HTML rule.
-   */
   Drupal.FilterStatus.prototype.addHTMLRule = function (rule) {
     this.rules.push(rule);
   };
 
-  /**
-   * A text filter HTML rule object.
-   *
-   * Intended to be used in combination with {@link Drupal.FilterStatus}.
-   *
-   * A text filter rule object describes:
-   *  1. allowed or forbidden tags: (optional) whitelist or blacklist HTML tags
-   *  2. restricted tag properties: (optional) whitelist or blacklist
-   *     attributes, styles and classes on a set of HTML tags.
-   *
-   * Typically, each text filter rule object does either 1 or 2, not both.
-   *
-   * The structure can be very clearly seen below:
-   *  1. use the "tags" key to list HTML tags, and set the "allow" key to
-   *     either true (to allow these HTML tags) or false (to forbid these HTML
-   *     tags). If you leave the "tags" key's default value (the empty array),
-   *     no restrictions are applied.
-   *  2. all nested within the "restrictedTags" key: use the "tags" subkey to
-   *     list HTML tags to which you want to apply property restrictions, then
-   *     use the "allowed" subkey to whitelist specific property values, and
-   *     similarly use the "forbidden" subkey to blacklist specific property
-   *     values.
-   *
-   * @example
-   * <caption>Whitelist the "p", "strong" and "a" HTML tags.</caption>
-   * {
-   *   tags: ['p', 'strong', 'a'],
-   *   allow: true,
-   *   restrictedTags: {
-   *     tags: [],
-   *     allowed: { attributes: [], styles: [], classes: [] },
-   *     forbidden: { attributes: [], styles: [], classes: [] }
-   *   }
-   * }
-   * @example
-   * <caption>For the "a" HTML tag, only allow the "href" attribute
-   * and the "external" class and disallow the "target" attribute.</caption>
-   * {
-   *   tags: [],
-   *   allow: null,
-   *   restrictedTags: {
-   *     tags: ['a'],
-   *     allowed: { attributes: ['href'], styles: [], classes: ['external'] },
-   *     forbidden: { attributes: ['target'], styles: [], classes: [] }
-   *   }
-   * }
-   * @example
-   * <caption>For all tags, allow the "data-*" attribute (that is, any
-   * attribute that begins with "data-").</caption>
-   * {
-   *   tags: [],
-   *   allow: null,
-   *   restrictedTags: {
-   *     tags: ['*'],
-   *     allowed: { attributes: ['data-*'], styles: [], classes: [] },
-   *     forbidden: { attributes: [], styles: [], classes: [] }
-   *   }
-   * }
-   *
-   * @return {object}
-   *   An object with the following structure:
-   * ```
-   * {
-   *   tags: Array,
-   *   allow: null,
-   *   restrictedTags: {
-   *     tags: Array,
-   *     allowed: {attributes: Array, styles: Array, classes: Array},
-   *     forbidden: {attributes: Array, styles: Array, classes: Array}
-   *   }
-   * }
-   * ```
-   *
-   * @see Drupal.FilterStatus
-   */
   Drupal.FilterHTMLRule = function () {
-    // Allow or forbid tags.
     this.tags = [];
     this.allow = null;
 
-    // Apply restrictions to properties set on tags.
     this.restrictedTags = {
       tags: [],
-      allowed: {attributes: [], styles: [], classes: []},
-      forbidden: {attributes: [], styles: [], classes: []}
+      allowed: { attributes: [], styles: [], classes: [] },
+      forbidden: { attributes: [], styles: [], classes: [] }
     };
 
     return this;
@@ -849,52 +329,16 @@
     return clone;
   };
 
-  /**
-   * Tracks the configuration of all text filters in {@link Drupal.FilterStatus}
-   * objects for {@link Drupal.editorConfiguration.featureIsAllowedByFilters}.
-   *
-   * @namespace
-   */
   Drupal.filterConfiguration = {
-
-    /**
-     * Drupal.FilterStatus objects, keyed by filter ID.
-     *
-     * @type {Object.<string, Drupal.FilterStatus>}
-     */
     statuses: {},
 
-    /**
-     * Live filter setting parsers.
-     *
-     * Object keyed by filter ID, for those filters that implement it.
-     *
-     * Filters should load the implementing JavaScript on the filter
-     * configuration form and implement
-     * `Drupal.filterSettings[filterID].getRules()`, which should return an
-     * array of {@link Drupal.FilterHTMLRule} objects.
-     *
-     * @namespace
-     */
     liveSettingParsers: {},
 
-    /**
-     * Updates all {@link Drupal.FilterStatus} objects to reflect current state.
-     *
-     * Automatically checks whether a filter is currently enabled or not. To
-     * support more finegrained.
-     *
-     * If a filter implements a live setting parser, then that will be used to
-     * keep the HTML rules for the {@link Drupal.FilterStatus} object
-     * up-to-date.
-     */
-    update: function () {
+    update: function update() {
       for (var filterID in Drupal.filterConfiguration.statuses) {
         if (Drupal.filterConfiguration.statuses.hasOwnProperty(filterID)) {
-          // Update status.
           Drupal.filterConfiguration.statuses[filterID].active = $('[name="filters[' + filterID + '][status]"]').is(':checked');
 
-          // Update current rules.
           if (Drupal.filterConfiguration.liveSettingParsers[filterID]) {
             Drupal.filterConfiguration.statuses[filterID].rules = Drupal.filterConfiguration.liveSettingParsers[filterID].getRules();
           }
@@ -904,32 +348,18 @@
 
   };
 
-  /**
-   * Initializes {@link Drupal.filterConfiguration}.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Gets filter configuration from filter form input.
-   */
   Drupal.behaviors.initializeFilterConfiguration = {
-    attach: function (context, settings) {
+    attach: function attach(context, settings) {
       var $context = $(context);
 
       $context.find('#filters-status-wrapper input.form-checkbox').once('filter-editor-status').each(function () {
         var $checkbox = $(this);
         var nameAttribute = $checkbox.attr('name');
 
-        // The filter's checkbox has a name attribute of the form
-        // "filters[<name of filter>][status]", parse "<name of filter>"
-        // from it.
         var filterID = nameAttribute.substring(8, nameAttribute.indexOf(']'));
 
-        // Create a Drupal.FilterStatus object to track the state (whether it's
-        // active or not and its current settings, if any) of each filter.
         Drupal.filterConfiguration.statuses[filterID] = new Drupal.FilterStatus(filterID);
       });
     }
   };
-
-})(jQuery, _, Drupal, document);
+})(jQuery, _, Drupal, document);
\ No newline at end of file
diff --git a/core/modules/editor/js/editor.dialog.es6.js b/core/modules/editor/js/editor.dialog.es6.js
new file mode 100644
index 000000000000..3bf6120c7af6
--- /dev/null
+++ b/core/modules/editor/js/editor.dialog.es6.js
@@ -0,0 +1,34 @@
+/**
+ * @file
+ * AJAX commands used by Editor module.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * Command to save the contents of an editor-provided modal.
+   *
+   * This command does not close the open modal. It should be followed by a
+   * call to `Drupal.AjaxCommands.prototype.closeDialog`. Editors that are
+   * integrated with dialogs must independently listen for an
+   * `editor:dialogsave` event to save the changes into the contents of their
+   * interface.
+   *
+   * @param {Drupal.Ajax} [ajax]
+   *   The Drupal.Ajax object.
+   * @param {object} response
+   *   The server response from the ajax request.
+   * @param {Array} response.values
+   *   The values that were saved.
+   * @param {number} [status]
+   *   The status code from the ajax request.
+   *
+   * @fires event:editor:dialogsave
+   */
+  Drupal.AjaxCommands.prototype.editorDialogSave = function (ajax, response, status) {
+    $(window).trigger('editor:dialogsave', [response.values]);
+  };
+
+})(jQuery, Drupal);
diff --git a/core/modules/editor/js/editor.dialog.js b/core/modules/editor/js/editor.dialog.js
index 3bf6120c7af6..bba05a7665e1 100644
--- a/core/modules/editor/js/editor.dialog.js
+++ b/core/modules/editor/js/editor.dialog.js
@@ -1,34 +1,16 @@
 /**
- * @file
- * AJAX commands used by Editor module.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/editor/js/editor.dialog.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   * Command to save the contents of an editor-provided modal.
-   *
-   * This command does not close the open modal. It should be followed by a
-   * call to `Drupal.AjaxCommands.prototype.closeDialog`. Editors that are
-   * integrated with dialogs must independently listen for an
-   * `editor:dialogsave` event to save the changes into the contents of their
-   * interface.
-   *
-   * @param {Drupal.Ajax} [ajax]
-   *   The Drupal.Ajax object.
-   * @param {object} response
-   *   The server response from the ajax request.
-   * @param {Array} response.values
-   *   The values that were saved.
-   * @param {number} [status]
-   *   The status code from the ajax request.
-   *
-   * @fires event:editor:dialogsave
-   */
   Drupal.AjaxCommands.prototype.editorDialogSave = function (ajax, response, status) {
     $(window).trigger('editor:dialogsave', [response.values]);
   };
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/modules/editor/js/editor.es6.js b/core/modules/editor/js/editor.es6.js
new file mode 100644
index 000000000000..2be16f57c5d1
--- /dev/null
+++ b/core/modules/editor/js/editor.es6.js
@@ -0,0 +1,318 @@
+/**
+ * @file
+ * Attaches behavior for the Editor module.
+ */
+
+(function ($, Drupal, drupalSettings) {
+
+  'use strict';
+
+  /**
+   * Finds the text area field associated with the given text format selector.
+   *
+   * @param {jQuery} $formatSelector
+   *   A text format selector DOM element.
+   *
+   * @return {HTMLElement}
+   *   The text area DOM element, if it was found.
+   */
+  function findFieldForFormatSelector($formatSelector) {
+    var field_id = $formatSelector.attr('data-editor-for');
+    // This selector will only find text areas in the top-level document. We do
+    // not support attaching editors on text areas within iframes.
+    return $('#' + field_id).get(0);
+  }
+
+  /**
+   * Changes the text editor on a text area.
+   *
+   * @param {HTMLElement} field
+   *   The text area DOM element.
+   * @param {string} newFormatID
+   *   The text format we're changing to; the text editor for the currently
+   *   active text format will be detached, and the text editor for the new text
+   *   format will be attached.
+   */
+  function changeTextEditor(field, newFormatID) {
+    var previousFormatID = field.getAttribute('data-editor-active-text-format');
+
+    // Detach the current editor (if any) and attach a new editor.
+    if (drupalSettings.editor.formats[previousFormatID]) {
+      Drupal.editorDetach(field, drupalSettings.editor.formats[previousFormatID]);
+    }
+    // When no text editor is currently active, stop tracking changes.
+    else {
+      $(field).off('.editor');
+    }
+
+    // Attach the new text editor (if any).
+    if (drupalSettings.editor.formats[newFormatID]) {
+      var format = drupalSettings.editor.formats[newFormatID];
+      filterXssWhenSwitching(field, format, previousFormatID, Drupal.editorAttach);
+    }
+
+    // Store the new active format.
+    field.setAttribute('data-editor-active-text-format', newFormatID);
+  }
+
+  /**
+   * Handles changes in text format.
+   *
+   * @param {jQuery.Event} event
+   *   The text format change event.
+   */
+  function onTextFormatChange(event) {
+    var $select = $(event.target);
+    var field = event.data.field;
+    var activeFormatID = field.getAttribute('data-editor-active-text-format');
+    var newFormatID = $select.val();
+
+    // Prevent double-attaching if the change event is triggered manually.
+    if (newFormatID === activeFormatID) {
+      return;
+    }
+
+    // When changing to a text format that has a text editor associated
+    // with it that supports content filtering, then first ask for
+    // confirmation, because switching text formats might cause certain
+    // markup to be stripped away.
+    var supportContentFiltering = drupalSettings.editor.formats[newFormatID] && drupalSettings.editor.formats[newFormatID].editorSupportsContentFiltering;
+    // If there is no content yet, it's always safe to change the text format.
+    var hasContent = field.value !== '';
+    if (hasContent && supportContentFiltering) {
+      var message = Drupal.t('Changing the text format to %text_format will permanently remove content that is not allowed in that text format.<br><br>Save your changes before switching the text format to avoid losing data.', {
+        '%text_format': $select.find('option:selected').text()
+      });
+      var confirmationDialog = Drupal.dialog('<div>' + message + '</div>', {
+        title: Drupal.t('Change text format?'),
+        dialogClass: 'editor-change-text-format-modal',
+        resizable: false,
+        buttons: [
+          {
+            text: Drupal.t('Continue'),
+            class: 'button button--primary',
+            click: function () {
+              changeTextEditor(field, newFormatID);
+              confirmationDialog.close();
+            }
+          },
+          {
+            text: Drupal.t('Cancel'),
+            class: 'button',
+            click: function () {
+              // Restore the active format ID: cancel changing text format. We
+              // cannot simply call event.preventDefault() because jQuery's
+              // change event is only triggered after the change has already
+              // been accepted.
+              $select.val(activeFormatID);
+              confirmationDialog.close();
+            }
+          }
+        ],
+        // Prevent this modal from being closed without the user making a choice
+        // as per http://stackoverflow.com/a/5438771.
+        closeOnEscape: false,
+        create: function () {
+          $(this).parent().find('.ui-dialog-titlebar-close').remove();
+        },
+        beforeClose: false,
+        close: function (event) {
+          // Automatically destroy the DOM element that was used for the dialog.
+          $(event.target).remove();
+        }
+      });
+
+      confirmationDialog.showModal();
+    }
+    else {
+      changeTextEditor(field, newFormatID);
+    }
+  }
+
+  /**
+   * Initialize an empty object for editors to place their attachment code.
+   *
+   * @namespace
+   */
+  Drupal.editors = {};
+
+  /**
+   * Enables editors on text_format elements.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches an editor to an input element.
+   * @prop {Drupal~behaviorDetach} detach
+   *   Detaches an editor from an input element.
+   */
+  Drupal.behaviors.editor = {
+    attach: function (context, settings) {
+      // If there are no editor settings, there are no editors to enable.
+      if (!settings.editor) {
+        return;
+      }
+
+      $(context).find('[data-editor-for]').once('editor').each(function () {
+        var $this = $(this);
+        var field = findFieldForFormatSelector($this);
+
+        // Opt-out if no supported text area was found.
+        if (!field) {
+          return;
+        }
+
+        // Store the current active format.
+        var activeFormatID = $this.val();
+        field.setAttribute('data-editor-active-text-format', activeFormatID);
+
+        // Directly attach this text editor, if the text format is enabled.
+        if (settings.editor.formats[activeFormatID]) {
+          // XSS protection for the current text format/editor is performed on
+          // the server side, so we don't need to do anything special here.
+          Drupal.editorAttach(field, settings.editor.formats[activeFormatID]);
+        }
+        // When there is no text editor for this text format, still track
+        // changes, because the user has the ability to switch to some text
+        // editor, otherwise this code would not be executed.
+        $(field).on('change.editor keypress.editor', function () {
+          field.setAttribute('data-editor-value-is-changed', 'true');
+          // Just knowing that the value was changed is enough, stop tracking.
+          $(field).off('.editor');
+        });
+
+        // Attach onChange handler to text format selector element.
+        if ($this.is('select')) {
+          $this.on('change.editorAttach', {field: field}, onTextFormatChange);
+        }
+        // Detach any editor when the containing form is submitted.
+        $this.parents('form').on('submit', function (event) {
+          // Do not detach if the event was canceled.
+          if (event.isDefaultPrevented()) {
+            return;
+          }
+          // Detach the current editor (if any).
+          if (settings.editor.formats[activeFormatID]) {
+            Drupal.editorDetach(field, settings.editor.formats[activeFormatID], 'serialize');
+          }
+        });
+      });
+    },
+
+    detach: function (context, settings, trigger) {
+      var editors;
+      // The 'serialize' trigger indicates that we should simply update the
+      // underlying element with the new text, without destroying the editor.
+      if (trigger === 'serialize') {
+        // Removing the editor-processed class guarantees that the editor will
+        // be reattached. Only do this if we're planning to destroy the editor.
+        editors = $(context).find('[data-editor-for]').findOnce('editor');
+      }
+      else {
+        editors = $(context).find('[data-editor-for]').removeOnce('editor');
+      }
+
+      editors.each(function () {
+        var $this = $(this);
+        var activeFormatID = $this.val();
+        var field = findFieldForFormatSelector($this);
+        if (field && activeFormatID in settings.editor.formats) {
+          Drupal.editorDetach(field, settings.editor.formats[activeFormatID], trigger);
+        }
+      });
+    }
+  };
+
+  /**
+   * Attaches editor behaviors to the field.
+   *
+   * @param {HTMLElement} field
+   *   The textarea DOM element.
+   * @param {object} format
+   *   The text format that's being activated, from
+   *   drupalSettings.editor.formats.
+   *
+   * @listens event:change
+   *
+   * @fires event:formUpdated
+   */
+  Drupal.editorAttach = function (field, format) {
+    if (format.editor) {
+      // Attach the text editor.
+      Drupal.editors[format.editor].attach(field, format);
+
+      // Ensures form.js' 'formUpdated' event is triggered even for changes that
+      // happen within the text editor.
+      Drupal.editors[format.editor].onChange(field, function () {
+        $(field).trigger('formUpdated');
+
+        // Keep track of changes, so we know what to do when switching text
+        // formats and guaranteeing XSS protection.
+        field.setAttribute('data-editor-value-is-changed', 'true');
+      });
+    }
+  };
+
+  /**
+   * Detaches editor behaviors from the field.
+   *
+   * @param {HTMLElement} field
+   *   The textarea DOM element.
+   * @param {object} format
+   *   The text format that's being activated, from
+   *   drupalSettings.editor.formats.
+   * @param {string} trigger
+   *   Trigger value from the detach behavior.
+   */
+  Drupal.editorDetach = function (field, format, trigger) {
+    if (format.editor) {
+      Drupal.editors[format.editor].detach(field, format, trigger);
+
+      // Restore the original value if the user didn't make any changes yet.
+      if (field.getAttribute('data-editor-value-is-changed') === 'false') {
+        field.value = field.getAttribute('data-editor-value-original');
+      }
+    }
+  };
+
+  /**
+   * Filter away XSS attack vectors when switching text formats.
+   *
+   * @param {HTMLElement} field
+   *   The textarea DOM element.
+   * @param {object} format
+   *   The text format that's being activated, from
+   *   drupalSettings.editor.formats.
+   * @param {string} originalFormatID
+   *   The text format ID of the original text format.
+   * @param {function} callback
+   *   A callback to be called (with no parameters) after the field's value has
+   *   been XSS filtered.
+   */
+  function filterXssWhenSwitching(field, format, originalFormatID, callback) {
+    // A text editor that already is XSS-safe needs no additional measures.
+    if (format.editor.isXssSafe) {
+      callback(field, format);
+    }
+    // Otherwise, ensure XSS safety: let the server XSS filter this value.
+    else {
+      $.ajax({
+        url: Drupal.url('editor/filter_xss/' + format.format),
+        type: 'POST',
+        data: {
+          value: field.value,
+          original_format_id: originalFormatID
+        },
+        dataType: 'json',
+        success: function (xssFilteredValue) {
+          // If the server returns false, then no XSS filtering is needed.
+          if (xssFilteredValue !== false) {
+            field.value = xssFilteredValue;
+          }
+          callback(field, format);
+        }
+      });
+    }
+  }
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/editor/js/editor.formattedTextEditor.es6.js b/core/modules/editor/js/editor.formattedTextEditor.es6.js
new file mode 100644
index 000000000000..fec73e03a0bf
--- /dev/null
+++ b/core/modules/editor/js/editor.formattedTextEditor.es6.js
@@ -0,0 +1,231 @@
+/**
+ * @file
+ * Text editor-based in-place editor for formatted text content in Drupal.
+ *
+ * Depends on editor.module. Works with any (WYSIWYG) editor that implements the
+ * editor.js API, including the optional attachInlineEditor() and onChange()
+ * methods.
+ * For example, assuming that a hypothetical editor's name was "Magical Editor"
+ * and its editor.js API implementation lived at Drupal.editors.magical, this
+ * JavaScript would use:
+ *  - Drupal.editors.magical.attachInlineEditor()
+ */
+
+(function ($, Drupal, drupalSettings, _) {
+
+  'use strict';
+
+  Drupal.quickedit.editors.editor = Drupal.quickedit.EditorView.extend(/** @lends Drupal.quickedit.editors.editor# */{
+
+    /**
+     * The text format for this field.
+     *
+     * @type {string}
+     */
+    textFormat: null,
+
+    /**
+     * Indicates whether this text format has transformations.
+     *
+     * @type {bool}
+     */
+    textFormatHasTransformations: null,
+
+    /**
+     * Stores a reference to the text editor object for this field.
+     *
+     * @type {Drupal.quickedit.EditorModel}
+     */
+    textEditor: null,
+
+    /**
+     * Stores the textual DOM element that is being in-place edited.
+     *
+     * @type {jQuery}
+     */
+    $textElement: null,
+
+    /**
+     * @constructs
+     *
+     * @augments Drupal.quickedit.EditorView
+     *
+     * @param {object} options
+     *   Options for the editor view.
+     */
+    initialize: function (options) {
+      Drupal.quickedit.EditorView.prototype.initialize.call(this, options);
+
+      var metadata = Drupal.quickedit.metadata.get(this.fieldModel.get('fieldID'), 'custom');
+      this.textFormat = drupalSettings.editor.formats[metadata.format];
+      this.textFormatHasTransformations = metadata.formatHasTransformations;
+      this.textEditor = Drupal.editors[this.textFormat.editor];
+
+      // Store the actual value of this field. We'll need this to restore the
+      // original value when the user discards his modifications.
+      var $fieldItems = this.$el.find('.quickedit-field');
+      if ($fieldItems.length) {
+        this.$textElement = $fieldItems.eq(0);
+      }
+      else {
+        this.$textElement = this.$el;
+      }
+      this.model.set('originalValue', this.$textElement.html());
+    },
+
+    /**
+     * @inheritdoc
+     *
+     * @return {jQuery}
+     *   The text element edited.
+     */
+    getEditedElement: function () {
+      return this.$textElement;
+    },
+
+    /**
+     * @inheritdoc
+     *
+     * @param {object} fieldModel
+     *   The field model.
+     * @param {string} state
+     *   The current state.
+     */
+    stateChange: function (fieldModel, state) {
+      var editorModel = this.model;
+      var from = fieldModel.previous('state');
+      var to = state;
+      switch (to) {
+        case 'inactive':
+          break;
+
+        case 'candidate':
+          // Detach the text editor when entering the 'candidate' state from one
+          // of the states where it could have been attached.
+          if (from !== 'inactive' && from !== 'highlighted') {
+            this.textEditor.detach(this.$textElement.get(0), this.textFormat);
+          }
+          // A field model's editor view revert() method is invoked when an
+          // 'active' field becomes a 'candidate' field. But, in the case of
+          // this in-place editor, the content will have been *replaced* if the
+          // text format has transformation filters. Therefore, if we stop
+          // in-place editing this entity, revert explicitly.
+          if (from === 'active' && this.textFormatHasTransformations) {
+            this.revert();
+          }
+          if (from === 'invalid') {
+            this.removeValidationErrors();
+          }
+          break;
+
+        case 'highlighted':
+          break;
+
+        case 'activating':
+          // When transformation filters have been applied to the formatted text
+          // of this field, then we'll need to load a re-formatted version of it
+          // without the transformation filters.
+          if (this.textFormatHasTransformations) {
+            var $textElement = this.$textElement;
+            this._getUntransformedText(function (untransformedText) {
+              $textElement.html(untransformedText);
+              fieldModel.set('state', 'active');
+            });
+          }
+          // When no transformation filters have been applied: start WYSIWYG
+          // editing immediately!
+          else {
+            // Defer updating the model until the current state change has
+            // propagated, to not trigger a nested state change event.
+            _.defer(function () {
+              fieldModel.set('state', 'active');
+            });
+          }
+          break;
+
+        case 'active':
+          var textElement = this.$textElement.get(0);
+          var toolbarView = fieldModel.toolbarView;
+          this.textEditor.attachInlineEditor(
+            textElement,
+            this.textFormat,
+            toolbarView.getMainWysiwygToolgroupId(),
+            toolbarView.getFloatedWysiwygToolgroupId()
+          );
+          // Set the state to 'changed' whenever the content has changed.
+          this.textEditor.onChange(textElement, function (htmlText) {
+            editorModel.set('currentValue', htmlText);
+            fieldModel.set('state', 'changed');
+          });
+          break;
+
+        case 'changed':
+          break;
+
+        case 'saving':
+          if (from === 'invalid') {
+            this.removeValidationErrors();
+          }
+          this.save();
+          break;
+
+        case 'saved':
+          break;
+
+        case 'invalid':
+          this.showValidationErrors();
+          break;
+      }
+    },
+
+    /**
+     * @inheritdoc
+     *
+     * @return {object}
+     *   The sttings for the quick edit UI.
+     */
+    getQuickEditUISettings: function () {
+      return {padding: true, unifiedToolbar: true, fullWidthToolbar: true, popup: false};
+    },
+
+    /**
+     * @inheritdoc
+     */
+    revert: function () {
+      this.$textElement.html(this.model.get('originalValue'));
+    },
+
+    /**
+     * Loads untransformed text for this field.
+     *
+     * More accurately: it re-filters formatted text to exclude transformation
+     * filters used by the text format.
+     *
+     * @param {function} callback
+     *   A callback function that will receive the untransformed text.
+     *
+     * @see \Drupal\editor\Ajax\GetUntransformedTextCommand
+     */
+    _getUntransformedText: function (callback) {
+      var fieldID = this.fieldModel.get('fieldID');
+
+      // Create a Drupal.ajax instance to load the form.
+      var textLoaderAjax = Drupal.ajax({
+        url: Drupal.quickedit.util.buildUrl(fieldID, Drupal.url('editor/!entity_type/!id/!field_name/!langcode/!view_mode')),
+        submit: {nocssjs: true}
+      });
+
+      // Implement a scoped editorGetUntransformedText AJAX command: calls the
+      // callback.
+      textLoaderAjax.commands.editorGetUntransformedText = function (ajax, response, status) {
+        callback(response.data);
+      };
+
+      // This will ensure our scoped editorGetUntransformedText AJAX command
+      // gets called.
+      textLoaderAjax.execute();
+    }
+
+  });
+
+})(jQuery, Drupal, drupalSettings, _);
diff --git a/core/modules/editor/js/editor.formattedTextEditor.js b/core/modules/editor/js/editor.formattedTextEditor.js
index fec73e03a0bf..470a3c60c79f 100644
--- a/core/modules/editor/js/editor.formattedTextEditor.js
+++ b/core/modules/editor/js/editor.formattedTextEditor.js
@@ -1,59 +1,25 @@
 /**
- * @file
- * Text editor-based in-place editor for formatted text content in Drupal.
- *
- * Depends on editor.module. Works with any (WYSIWYG) editor that implements the
- * editor.js API, including the optional attachInlineEditor() and onChange()
- * methods.
- * For example, assuming that a hypothetical editor's name was "Magical Editor"
- * and its editor.js API implementation lived at Drupal.editors.magical, this
- * JavaScript would use:
- *  - Drupal.editors.magical.attachInlineEditor()
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/editor/js/editor.formattedTextEditor.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings, _) {
 
   'use strict';
 
-  Drupal.quickedit.editors.editor = Drupal.quickedit.EditorView.extend(/** @lends Drupal.quickedit.editors.editor# */{
-
-    /**
-     * The text format for this field.
-     *
-     * @type {string}
-     */
+  Drupal.quickedit.editors.editor = Drupal.quickedit.EditorView.extend({
     textFormat: null,
 
-    /**
-     * Indicates whether this text format has transformations.
-     *
-     * @type {bool}
-     */
     textFormatHasTransformations: null,
 
-    /**
-     * Stores a reference to the text editor object for this field.
-     *
-     * @type {Drupal.quickedit.EditorModel}
-     */
     textEditor: null,
 
-    /**
-     * Stores the textual DOM element that is being in-place edited.
-     *
-     * @type {jQuery}
-     */
     $textElement: null,
 
-    /**
-     * @constructs
-     *
-     * @augments Drupal.quickedit.EditorView
-     *
-     * @param {object} options
-     *   Options for the editor view.
-     */
-    initialize: function (options) {
+    initialize: function initialize(options) {
       Drupal.quickedit.EditorView.prototype.initialize.call(this, options);
 
       var metadata = Drupal.quickedit.metadata.get(this.fieldModel.get('fieldID'), 'custom');
@@ -61,37 +27,20 @@
       this.textFormatHasTransformations = metadata.formatHasTransformations;
       this.textEditor = Drupal.editors[this.textFormat.editor];
 
-      // Store the actual value of this field. We'll need this to restore the
-      // original value when the user discards his modifications.
       var $fieldItems = this.$el.find('.quickedit-field');
       if ($fieldItems.length) {
         this.$textElement = $fieldItems.eq(0);
-      }
-      else {
+      } else {
         this.$textElement = this.$el;
       }
       this.model.set('originalValue', this.$textElement.html());
     },
 
-    /**
-     * @inheritdoc
-     *
-     * @return {jQuery}
-     *   The text element edited.
-     */
-    getEditedElement: function () {
+    getEditedElement: function getEditedElement() {
       return this.$textElement;
     },
 
-    /**
-     * @inheritdoc
-     *
-     * @param {object} fieldModel
-     *   The field model.
-     * @param {string} state
-     *   The current state.
-     */
-    stateChange: function (fieldModel, state) {
+    stateChange: function stateChange(fieldModel, state) {
       var editorModel = this.model;
       var from = fieldModel.previous('state');
       var to = state;
@@ -100,16 +49,10 @@
           break;
 
         case 'candidate':
-          // Detach the text editor when entering the 'candidate' state from one
-          // of the states where it could have been attached.
           if (from !== 'inactive' && from !== 'highlighted') {
             this.textEditor.detach(this.$textElement.get(0), this.textFormat);
           }
-          // A field model's editor view revert() method is invoked when an
-          // 'active' field becomes a 'candidate' field. But, in the case of
-          // this in-place editor, the content will have been *replaced* if the
-          // text format has transformation filters. Therefore, if we stop
-          // in-place editing this entity, revert explicitly.
+
           if (from === 'active' && this.textFormatHasTransformations) {
             this.revert();
           }
@@ -122,37 +65,24 @@
           break;
 
         case 'activating':
-          // When transformation filters have been applied to the formatted text
-          // of this field, then we'll need to load a re-formatted version of it
-          // without the transformation filters.
           if (this.textFormatHasTransformations) {
             var $textElement = this.$textElement;
             this._getUntransformedText(function (untransformedText) {
               $textElement.html(untransformedText);
               fieldModel.set('state', 'active');
             });
-          }
-          // When no transformation filters have been applied: start WYSIWYG
-          // editing immediately!
-          else {
-            // Defer updating the model until the current state change has
-            // propagated, to not trigger a nested state change event.
-            _.defer(function () {
-              fieldModel.set('state', 'active');
-            });
-          }
+          } else {
+              _.defer(function () {
+                fieldModel.set('state', 'active');
+              });
+            }
           break;
 
         case 'active':
           var textElement = this.$textElement.get(0);
           var toolbarView = fieldModel.toolbarView;
-          this.textEditor.attachInlineEditor(
-            textElement,
-            this.textFormat,
-            toolbarView.getMainWysiwygToolgroupId(),
-            toolbarView.getFloatedWysiwygToolgroupId()
-          );
-          // Set the state to 'changed' whenever the content has changed.
+          this.textEditor.attachInlineEditor(textElement, this.textFormat, toolbarView.getMainWysiwygToolgroupId(), toolbarView.getFloatedWysiwygToolgroupId());
+
           this.textEditor.onChange(textElement, function (htmlText) {
             editorModel.set('currentValue', htmlText);
             fieldModel.set('state', 'changed');
@@ -178,54 +108,28 @@
       }
     },
 
-    /**
-     * @inheritdoc
-     *
-     * @return {object}
-     *   The sttings for the quick edit UI.
-     */
-    getQuickEditUISettings: function () {
-      return {padding: true, unifiedToolbar: true, fullWidthToolbar: true, popup: false};
+    getQuickEditUISettings: function getQuickEditUISettings() {
+      return { padding: true, unifiedToolbar: true, fullWidthToolbar: true, popup: false };
     },
 
-    /**
-     * @inheritdoc
-     */
-    revert: function () {
+    revert: function revert() {
       this.$textElement.html(this.model.get('originalValue'));
     },
 
-    /**
-     * Loads untransformed text for this field.
-     *
-     * More accurately: it re-filters formatted text to exclude transformation
-     * filters used by the text format.
-     *
-     * @param {function} callback
-     *   A callback function that will receive the untransformed text.
-     *
-     * @see \Drupal\editor\Ajax\GetUntransformedTextCommand
-     */
-    _getUntransformedText: function (callback) {
+    _getUntransformedText: function _getUntransformedText(callback) {
       var fieldID = this.fieldModel.get('fieldID');
 
-      // Create a Drupal.ajax instance to load the form.
       var textLoaderAjax = Drupal.ajax({
         url: Drupal.quickedit.util.buildUrl(fieldID, Drupal.url('editor/!entity_type/!id/!field_name/!langcode/!view_mode')),
-        submit: {nocssjs: true}
+        submit: { nocssjs: true }
       });
 
-      // Implement a scoped editorGetUntransformedText AJAX command: calls the
-      // callback.
       textLoaderAjax.commands.editorGetUntransformedText = function (ajax, response, status) {
         callback(response.data);
       };
 
-      // This will ensure our scoped editorGetUntransformedText AJAX command
-      // gets called.
       textLoaderAjax.execute();
     }
 
   });
-
-})(jQuery, Drupal, drupalSettings, _);
+})(jQuery, Drupal, drupalSettings, _);
\ No newline at end of file
diff --git a/core/modules/editor/js/editor.js b/core/modules/editor/js/editor.js
index 2be16f57c5d1..2c0fc15735d1 100644
--- a/core/modules/editor/js/editor.js
+++ b/core/modules/editor/js/editor.js
@@ -1,83 +1,50 @@
 /**
- * @file
- * Attaches behavior for the Editor module.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/editor/js/editor.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings) {
 
   'use strict';
 
-  /**
-   * Finds the text area field associated with the given text format selector.
-   *
-   * @param {jQuery} $formatSelector
-   *   A text format selector DOM element.
-   *
-   * @return {HTMLElement}
-   *   The text area DOM element, if it was found.
-   */
   function findFieldForFormatSelector($formatSelector) {
     var field_id = $formatSelector.attr('data-editor-for');
-    // This selector will only find text areas in the top-level document. We do
-    // not support attaching editors on text areas within iframes.
+
     return $('#' + field_id).get(0);
   }
 
-  /**
-   * Changes the text editor on a text area.
-   *
-   * @param {HTMLElement} field
-   *   The text area DOM element.
-   * @param {string} newFormatID
-   *   The text format we're changing to; the text editor for the currently
-   *   active text format will be detached, and the text editor for the new text
-   *   format will be attached.
-   */
   function changeTextEditor(field, newFormatID) {
     var previousFormatID = field.getAttribute('data-editor-active-text-format');
 
-    // Detach the current editor (if any) and attach a new editor.
     if (drupalSettings.editor.formats[previousFormatID]) {
       Drupal.editorDetach(field, drupalSettings.editor.formats[previousFormatID]);
-    }
-    // When no text editor is currently active, stop tracking changes.
-    else {
-      $(field).off('.editor');
-    }
+    } else {
+        $(field).off('.editor');
+      }
 
-    // Attach the new text editor (if any).
     if (drupalSettings.editor.formats[newFormatID]) {
       var format = drupalSettings.editor.formats[newFormatID];
       filterXssWhenSwitching(field, format, previousFormatID, Drupal.editorAttach);
     }
 
-    // Store the new active format.
     field.setAttribute('data-editor-active-text-format', newFormatID);
   }
 
-  /**
-   * Handles changes in text format.
-   *
-   * @param {jQuery.Event} event
-   *   The text format change event.
-   */
   function onTextFormatChange(event) {
     var $select = $(event.target);
     var field = event.data.field;
     var activeFormatID = field.getAttribute('data-editor-active-text-format');
     var newFormatID = $select.val();
 
-    // Prevent double-attaching if the change event is triggered manually.
     if (newFormatID === activeFormatID) {
       return;
     }
 
-    // When changing to a text format that has a text editor associated
-    // with it that supports content filtering, then first ask for
-    // confirmation, because switching text formats might cause certain
-    // markup to be stripped away.
     var supportContentFiltering = drupalSettings.editor.formats[newFormatID] && drupalSettings.editor.formats[newFormatID].editorSupportsContentFiltering;
-    // If there is no content yet, it's always safe to change the text format.
+
     var hasContent = field.value !== '';
     if (hasContent && supportContentFiltering) {
       var message = Drupal.t('Changing the text format to %text_format will permanently remove content that is not allowed in that text format.<br><br>Save your changes before switching the text format to avoid losing data.', {
@@ -87,68 +54,42 @@
         title: Drupal.t('Change text format?'),
         dialogClass: 'editor-change-text-format-modal',
         resizable: false,
-        buttons: [
-          {
-            text: Drupal.t('Continue'),
-            class: 'button button--primary',
-            click: function () {
-              changeTextEditor(field, newFormatID);
-              confirmationDialog.close();
-            }
-          },
-          {
-            text: Drupal.t('Cancel'),
-            class: 'button',
-            click: function () {
-              // Restore the active format ID: cancel changing text format. We
-              // cannot simply call event.preventDefault() because jQuery's
-              // change event is only triggered after the change has already
-              // been accepted.
-              $select.val(activeFormatID);
-              confirmationDialog.close();
-            }
+        buttons: [{
+          text: Drupal.t('Continue'),
+          class: 'button button--primary',
+          click: function click() {
+            changeTextEditor(field, newFormatID);
+            confirmationDialog.close();
+          }
+        }, {
+          text: Drupal.t('Cancel'),
+          class: 'button',
+          click: function click() {
+            $select.val(activeFormatID);
+            confirmationDialog.close();
           }
-        ],
-        // Prevent this modal from being closed without the user making a choice
-        // as per http://stackoverflow.com/a/5438771.
+        }],
+
         closeOnEscape: false,
-        create: function () {
+        create: function create() {
           $(this).parent().find('.ui-dialog-titlebar-close').remove();
         },
         beforeClose: false,
-        close: function (event) {
-          // Automatically destroy the DOM element that was used for the dialog.
+        close: function close(event) {
           $(event.target).remove();
         }
       });
 
       confirmationDialog.showModal();
-    }
-    else {
+    } else {
       changeTextEditor(field, newFormatID);
     }
   }
 
-  /**
-   * Initialize an empty object for editors to place their attachment code.
-   *
-   * @namespace
-   */
   Drupal.editors = {};
 
-  /**
-   * Enables editors on text_format elements.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches an editor to an input element.
-   * @prop {Drupal~behaviorDetach} detach
-   *   Detaches an editor from an input element.
-   */
   Drupal.behaviors.editor = {
-    attach: function (context, settings) {
-      // If there are no editor settings, there are no editors to enable.
+    attach: function attach(context, settings) {
       if (!settings.editor) {
         return;
       }
@@ -157,41 +98,32 @@
         var $this = $(this);
         var field = findFieldForFormatSelector($this);
 
-        // Opt-out if no supported text area was found.
         if (!field) {
           return;
         }
 
-        // Store the current active format.
         var activeFormatID = $this.val();
         field.setAttribute('data-editor-active-text-format', activeFormatID);
 
-        // Directly attach this text editor, if the text format is enabled.
         if (settings.editor.formats[activeFormatID]) {
-          // XSS protection for the current text format/editor is performed on
-          // the server side, so we don't need to do anything special here.
           Drupal.editorAttach(field, settings.editor.formats[activeFormatID]);
         }
-        // When there is no text editor for this text format, still track
-        // changes, because the user has the ability to switch to some text
-        // editor, otherwise this code would not be executed.
+
         $(field).on('change.editor keypress.editor', function () {
           field.setAttribute('data-editor-value-is-changed', 'true');
-          // Just knowing that the value was changed is enough, stop tracking.
+
           $(field).off('.editor');
         });
 
-        // Attach onChange handler to text format selector element.
         if ($this.is('select')) {
-          $this.on('change.editorAttach', {field: field}, onTextFormatChange);
+          $this.on('change.editorAttach', { field: field }, onTextFormatChange);
         }
-        // Detach any editor when the containing form is submitted.
+
         $this.parents('form').on('submit', function (event) {
-          // Do not detach if the event was canceled.
           if (event.isDefaultPrevented()) {
             return;
           }
-          // Detach the current editor (if any).
+
           if (settings.editor.formats[activeFormatID]) {
             Drupal.editorDetach(field, settings.editor.formats[activeFormatID], 'serialize');
           }
@@ -199,16 +131,12 @@
       });
     },
 
-    detach: function (context, settings, trigger) {
+    detach: function detach(context, settings, trigger) {
       var editors;
-      // The 'serialize' trigger indicates that we should simply update the
-      // underlying element with the new text, without destroying the editor.
+
       if (trigger === 'serialize') {
-        // Removing the editor-processed class guarantees that the editor will
-        // be reattached. Only do this if we're planning to destroy the editor.
         editors = $(context).find('[data-editor-for]').findOnce('editor');
-      }
-      else {
+      } else {
         editors = $(context).find('[data-editor-for]').removeOnce('editor');
       }
 
@@ -223,96 +151,47 @@
     }
   };
 
-  /**
-   * Attaches editor behaviors to the field.
-   *
-   * @param {HTMLElement} field
-   *   The textarea DOM element.
-   * @param {object} format
-   *   The text format that's being activated, from
-   *   drupalSettings.editor.formats.
-   *
-   * @listens event:change
-   *
-   * @fires event:formUpdated
-   */
   Drupal.editorAttach = function (field, format) {
     if (format.editor) {
-      // Attach the text editor.
       Drupal.editors[format.editor].attach(field, format);
 
-      // Ensures form.js' 'formUpdated' event is triggered even for changes that
-      // happen within the text editor.
       Drupal.editors[format.editor].onChange(field, function () {
         $(field).trigger('formUpdated');
 
-        // Keep track of changes, so we know what to do when switching text
-        // formats and guaranteeing XSS protection.
         field.setAttribute('data-editor-value-is-changed', 'true');
       });
     }
   };
 
-  /**
-   * Detaches editor behaviors from the field.
-   *
-   * @param {HTMLElement} field
-   *   The textarea DOM element.
-   * @param {object} format
-   *   The text format that's being activated, from
-   *   drupalSettings.editor.formats.
-   * @param {string} trigger
-   *   Trigger value from the detach behavior.
-   */
   Drupal.editorDetach = function (field, format, trigger) {
     if (format.editor) {
       Drupal.editors[format.editor].detach(field, format, trigger);
 
-      // Restore the original value if the user didn't make any changes yet.
       if (field.getAttribute('data-editor-value-is-changed') === 'false') {
         field.value = field.getAttribute('data-editor-value-original');
       }
     }
   };
 
-  /**
-   * Filter away XSS attack vectors when switching text formats.
-   *
-   * @param {HTMLElement} field
-   *   The textarea DOM element.
-   * @param {object} format
-   *   The text format that's being activated, from
-   *   drupalSettings.editor.formats.
-   * @param {string} originalFormatID
-   *   The text format ID of the original text format.
-   * @param {function} callback
-   *   A callback to be called (with no parameters) after the field's value has
-   *   been XSS filtered.
-   */
   function filterXssWhenSwitching(field, format, originalFormatID, callback) {
-    // A text editor that already is XSS-safe needs no additional measures.
     if (format.editor.isXssSafe) {
       callback(field, format);
-    }
-    // Otherwise, ensure XSS safety: let the server XSS filter this value.
-    else {
-      $.ajax({
-        url: Drupal.url('editor/filter_xss/' + format.format),
-        type: 'POST',
-        data: {
-          value: field.value,
-          original_format_id: originalFormatID
-        },
-        dataType: 'json',
-        success: function (xssFilteredValue) {
-          // If the server returns false, then no XSS filtering is needed.
-          if (xssFilteredValue !== false) {
-            field.value = xssFilteredValue;
+    } else {
+        $.ajax({
+          url: Drupal.url('editor/filter_xss/' + format.format),
+          type: 'POST',
+          data: {
+            value: field.value,
+            original_format_id: originalFormatID
+          },
+          dataType: 'json',
+          success: function success(xssFilteredValue) {
+            if (xssFilteredValue !== false) {
+              field.value = xssFilteredValue;
+            }
+            callback(field, format);
           }
-          callback(field, format);
-        }
-      });
-    }
+        });
+      }
   }
-
-})(jQuery, Drupal, drupalSettings);
+})(jQuery, Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/modules/field_ui/field_ui.es6.js b/core/modules/field_ui/field_ui.es6.js
new file mode 100644
index 000000000000..30e983a14348
--- /dev/null
+++ b/core/modules/field_ui/field_ui.es6.js
@@ -0,0 +1,335 @@
+/**
+ * @file
+ * Attaches the behaviors for the Field UI module.
+ */
+
+(function ($, Drupal, drupalSettings) {
+
+  'use strict';
+
+  /**
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Adds behaviors to the field storage add form.
+   */
+  Drupal.behaviors.fieldUIFieldStorageAddForm = {
+    attach: function (context) {
+      var $form = $(context).find('[data-drupal-selector="field-ui-field-storage-add-form"]').once('field_ui_add');
+      if ($form.length) {
+        // Add a few 'js-form-required' and 'form-required' css classes here.
+        // We can not use the Form API '#required' property because both label
+        // elements for "add new" and "re-use existing" can never be filled and
+        // submitted at the same time. The actual validation will happen
+        // server-side.
+        $form.find(
+          '.js-form-item-label label,' +
+          '.js-form-item-field-name label,' +
+          '.js-form-item-existing-storage-label label')
+          .addClass('js-form-required form-required');
+
+        var $newFieldType = $form.find('select[name="new_storage_type"]');
+        var $existingStorageName = $form.find('select[name="existing_storage_name"]');
+        var $existingStorageLabel = $form.find('input[name="existing_storage_label"]');
+
+        // When the user selects a new field type, clear the "existing field"
+        // selection.
+        $newFieldType.on('change', function () {
+          if ($(this).val() !== '') {
+            // Reset the "existing storage name" selection.
+            $existingStorageName.val('').trigger('change');
+          }
+        });
+
+        // When the user selects an existing storage name, clear the "new field
+        // type" selection and populate the 'existing_storage_label' element.
+        $existingStorageName.on('change', function () {
+          var value = $(this).val();
+          if (value !== '') {
+            // Reset the "new field type" selection.
+            $newFieldType.val('').trigger('change');
+
+            // Pre-populate the "existing storage label" element.
+            if (typeof drupalSettings.existingFieldLabels[value] !== 'undefined') {
+              $existingStorageLabel.val(drupalSettings.existingFieldLabels[value]);
+            }
+          }
+        });
+      }
+    }
+  };
+
+  /**
+   * Attaches the fieldUIOverview behavior.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches the fieldUIOverview behavior.
+   *
+   * @see Drupal.fieldUIOverview.attach
+   */
+  Drupal.behaviors.fieldUIDisplayOverview = {
+    attach: function (context, settings) {
+      $(context).find('table#field-display-overview').once('field-display-overview').each(function () {
+        Drupal.fieldUIOverview.attach(this, settings.fieldUIRowsData, Drupal.fieldUIDisplayOverview);
+      });
+    }
+  };
+
+  /**
+   * Namespace for the field UI overview.
+   *
+   * @namespace
+   */
+  Drupal.fieldUIOverview = {
+
+    /**
+     * Attaches the fieldUIOverview behavior.
+     *
+     * @param {HTMLTableElement} table
+     *   The table element for the overview.
+     * @param {object} rowsData
+     *   The data of the rows in the table.
+     * @param {object} rowHandlers
+     *   Handlers to be added to the rows.
+     */
+    attach: function (table, rowsData, rowHandlers) {
+      var tableDrag = Drupal.tableDrag[table.id];
+
+      // Add custom tabledrag callbacks.
+      tableDrag.onDrop = this.onDrop;
+      tableDrag.row.prototype.onSwap = this.onSwap;
+
+      // Create row handlers.
+      $(table).find('tr.draggable').each(function () {
+        // Extract server-side data for the row.
+        var row = this;
+        if (row.id in rowsData) {
+          var data = rowsData[row.id];
+          data.tableDrag = tableDrag;
+
+          // Create the row handler, make it accessible from the DOM row
+          // element.
+          var rowHandler = new rowHandlers[data.rowHandler](row, data);
+          $(row).data('fieldUIRowHandler', rowHandler);
+        }
+      });
+    },
+
+    /**
+     * Event handler to be attached to form inputs triggering a region change.
+     */
+    onChange: function () {
+      var $trigger = $(this);
+      var $row = $trigger.closest('tr');
+      var rowHandler = $row.data('fieldUIRowHandler');
+
+      var refreshRows = {};
+      refreshRows[rowHandler.name] = $trigger.get(0);
+
+      // Handle region change.
+      var region = rowHandler.getRegion();
+      if (region !== rowHandler.region) {
+        // Remove parenting.
+        $row.find('select.js-field-parent').val('');
+        // Let the row handler deal with the region change.
+        $.extend(refreshRows, rowHandler.regionChange(region));
+        // Update the row region.
+        rowHandler.region = region;
+      }
+
+      // Ajax-update the rows.
+      Drupal.fieldUIOverview.AJAXRefreshRows(refreshRows);
+    },
+
+    /**
+     * Lets row handlers react when a row is dropped into a new region.
+     */
+    onDrop: function () {
+      var dragObject = this;
+      var row = dragObject.rowObject.element;
+      var $row = $(row);
+      var rowHandler = $row.data('fieldUIRowHandler');
+      if (typeof rowHandler !== 'undefined') {
+        var regionRow = $row.prevAll('tr.region-message').get(0);
+        var region = regionRow.className.replace(/([^ ]+[ ]+)*region-([^ ]+)-message([ ]+[^ ]+)*/, '$2');
+
+        if (region !== rowHandler.region) {
+          // Let the row handler deal with the region change.
+          var refreshRows = rowHandler.regionChange(region);
+          // Update the row region.
+          rowHandler.region = region;
+          // Ajax-update the rows.
+          Drupal.fieldUIOverview.AJAXRefreshRows(refreshRows);
+        }
+      }
+    },
+
+    /**
+     * Refreshes placeholder rows in empty regions while a row is being dragged.
+     *
+     * Copied from block.js.
+     *
+     * @param {HTMLElement} draggedRow
+     *   The tableDrag rowObject for the row being dragged.
+     */
+    onSwap: function (draggedRow) {
+      var rowObject = this;
+      $(rowObject.table).find('tr.region-message').each(function () {
+        var $this = $(this);
+        // If the dragged row is in this region, but above the message row, swap
+        // it down one space.
+        if ($this.prev('tr').get(0) === rowObject.group[rowObject.group.length - 1]) {
+          // Prevent a recursion problem when using the keyboard to move rows
+          // up.
+          if ((rowObject.method !== 'keyboard' || rowObject.direction === 'down')) {
+            rowObject.swap('after', this);
+          }
+        }
+        // This region has become empty.
+        if ($this.next('tr').is(':not(.draggable)') || $this.next('tr').length === 0) {
+          $this.removeClass('region-populated').addClass('region-empty');
+        }
+        // This region has become populated.
+        else if ($this.is('.region-empty')) {
+          $this.removeClass('region-empty').addClass('region-populated');
+        }
+      });
+    },
+
+    /**
+     * Triggers Ajax refresh of selected rows.
+     *
+     * The 'format type' selects can trigger a series of changes in child rows.
+     * The #ajax behavior is therefore not attached directly to the selects, but
+     * triggered manually through a hidden #ajax 'Refresh' button.
+     *
+     * @param {object} rows
+     *   A hash object, whose keys are the names of the rows to refresh (they
+     *   will receive the 'ajax-new-content' effect on the server side), and
+     *   whose values are the DOM element in the row that should get an Ajax
+     *   throbber.
+     */
+    AJAXRefreshRows: function (rows) {
+      // Separate keys and values.
+      var rowNames = [];
+      var ajaxElements = [];
+      var rowName;
+      for (rowName in rows) {
+        if (rows.hasOwnProperty(rowName)) {
+          rowNames.push(rowName);
+          ajaxElements.push(rows[rowName]);
+        }
+      }
+
+      if (rowNames.length) {
+        // Add a throbber next each of the ajaxElements.
+        $(ajaxElements).after('<div class="ajax-progress ajax-progress-throbber"><div class="throbber">&nbsp;</div></div>');
+
+        // Fire the Ajax update.
+        $('input[name=refresh_rows]').val(rowNames.join(' '));
+        $('input[data-drupal-selector="edit-refresh"]').trigger('mousedown');
+
+        // Disabled elements do not appear in POST ajax data, so we mark the
+        // elements disabled only after firing the request.
+        $(ajaxElements).prop('disabled', true);
+      }
+    }
+  };
+
+  /**
+   * Row handlers for the 'Manage display' screen.
+   *
+   * @namespace
+   */
+  Drupal.fieldUIDisplayOverview = {};
+
+  /**
+   * Constructor for a 'field' row handler.
+   *
+   * This handler is used for both fields and 'extra fields' rows.
+   *
+   * @constructor
+   *
+   * @param {HTMLTableRowElement} row
+   *   The row DOM element.
+   * @param {object} data
+   *   Additional data to be populated in the constructed object.
+   *
+   * @return {Drupal.fieldUIDisplayOverview.field}
+   *   The field row handler constructed.
+   */
+  Drupal.fieldUIDisplayOverview.field = function (row, data) {
+    this.row = row;
+    this.name = data.name;
+    this.region = data.region;
+    this.tableDrag = data.tableDrag;
+    this.defaultPlugin = data.defaultPlugin;
+
+    // Attach change listener to the 'plugin type' select.
+    this.$pluginSelect = $(row).find('.field-plugin-type');
+    this.$pluginSelect.on('change', Drupal.fieldUIOverview.onChange);
+
+    // Attach change listener to the 'region' select.
+    this.$regionSelect = $(row).find('select.field-region');
+    this.$regionSelect.on('change', Drupal.fieldUIOverview.onChange);
+
+    return this;
+  };
+
+  Drupal.fieldUIDisplayOverview.field.prototype = {
+
+    /**
+     * Returns the region corresponding to the current form values of the row.
+     *
+     * @return {string}
+     *   Either 'hidden' or 'content'.
+     */
+    getRegion: function () {
+      return this.$regionSelect.val();
+    },
+
+    /**
+     * Reacts to a row being changed regions.
+     *
+     * This function is called when the row is moved to a different region, as
+     * a
+     * result of either :
+     * - a drag-and-drop action (the row's form elements then probably need to
+     * be updated accordingly)
+     * - user input in one of the form elements watched by the
+     *   {@link Drupal.fieldUIOverview.onChange} change listener.
+     *
+     * @param {string} region
+     *   The name of the new region for the row.
+     *
+     * @return {object}
+     *   A hash object indicating which rows should be Ajax-updated as a result
+     *   of the change, in the format expected by
+     *   {@link Drupal.fieldUIOverview.AJAXRefreshRows}.
+     */
+    regionChange: function (region) {
+      // Replace dashes with underscores.
+      region = region.replace(/-/g, '_');
+
+      // Set the region of the select list.
+      this.$regionSelect.val(region);
+
+      // Restore the formatter back to the default formatter. Pseudo-fields
+      // do not have default formatters, we just return to 'visible' for
+      // those.
+      var value = (typeof this.defaultPlugin !== 'undefined') ? this.defaultPlugin : this.$pluginSelect.find('option').val();
+
+      if (typeof value !== 'undefined') {
+        this.$pluginSelect.val(value);
+      }
+
+      var refreshRows = {};
+      refreshRows[this.name] = this.$pluginSelect.get(0);
+
+      return refreshRows;
+    }
+  };
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/field_ui/field_ui.js b/core/modules/field_ui/field_ui.js
index 30e983a14348..58f832c02124 100644
--- a/core/modules/field_ui/field_ui.js
+++ b/core/modules/field_ui/field_ui.js
@@ -1,55 +1,36 @@
 /**
- * @file
- * Attaches the behaviors for the Field UI module.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/field_ui/field_ui.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings) {
 
   'use strict';
 
-  /**
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Adds behaviors to the field storage add form.
-   */
   Drupal.behaviors.fieldUIFieldStorageAddForm = {
-    attach: function (context) {
+    attach: function attach(context) {
       var $form = $(context).find('[data-drupal-selector="field-ui-field-storage-add-form"]').once('field_ui_add');
       if ($form.length) {
-        // Add a few 'js-form-required' and 'form-required' css classes here.
-        // We can not use the Form API '#required' property because both label
-        // elements for "add new" and "re-use existing" can never be filled and
-        // submitted at the same time. The actual validation will happen
-        // server-side.
-        $form.find(
-          '.js-form-item-label label,' +
-          '.js-form-item-field-name label,' +
-          '.js-form-item-existing-storage-label label')
-          .addClass('js-form-required form-required');
+        $form.find('.js-form-item-label label,' + '.js-form-item-field-name label,' + '.js-form-item-existing-storage-label label').addClass('js-form-required form-required');
 
         var $newFieldType = $form.find('select[name="new_storage_type"]');
         var $existingStorageName = $form.find('select[name="existing_storage_name"]');
         var $existingStorageLabel = $form.find('input[name="existing_storage_label"]');
 
-        // When the user selects a new field type, clear the "existing field"
-        // selection.
         $newFieldType.on('change', function () {
           if ($(this).val() !== '') {
-            // Reset the "existing storage name" selection.
             $existingStorageName.val('').trigger('change');
           }
         });
 
-        // When the user selects an existing storage name, clear the "new field
-        // type" selection and populate the 'existing_storage_label' element.
         $existingStorageName.on('change', function () {
           var value = $(this).val();
           if (value !== '') {
-            // Reset the "new field type" selection.
             $newFieldType.val('').trigger('change');
 
-            // Pre-populate the "existing storage label" element.
             if (typeof drupalSettings.existingFieldLabels[value] !== 'undefined') {
               $existingStorageLabel.val(drupalSettings.existingFieldLabels[value]);
             }
@@ -59,68 +40,34 @@
     }
   };
 
-  /**
-   * Attaches the fieldUIOverview behavior.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches the fieldUIOverview behavior.
-   *
-   * @see Drupal.fieldUIOverview.attach
-   */
   Drupal.behaviors.fieldUIDisplayOverview = {
-    attach: function (context, settings) {
+    attach: function attach(context, settings) {
       $(context).find('table#field-display-overview').once('field-display-overview').each(function () {
         Drupal.fieldUIOverview.attach(this, settings.fieldUIRowsData, Drupal.fieldUIDisplayOverview);
       });
     }
   };
 
-  /**
-   * Namespace for the field UI overview.
-   *
-   * @namespace
-   */
   Drupal.fieldUIOverview = {
-
-    /**
-     * Attaches the fieldUIOverview behavior.
-     *
-     * @param {HTMLTableElement} table
-     *   The table element for the overview.
-     * @param {object} rowsData
-     *   The data of the rows in the table.
-     * @param {object} rowHandlers
-     *   Handlers to be added to the rows.
-     */
-    attach: function (table, rowsData, rowHandlers) {
+    attach: function attach(table, rowsData, rowHandlers) {
       var tableDrag = Drupal.tableDrag[table.id];
 
-      // Add custom tabledrag callbacks.
       tableDrag.onDrop = this.onDrop;
       tableDrag.row.prototype.onSwap = this.onSwap;
 
-      // Create row handlers.
       $(table).find('tr.draggable').each(function () {
-        // Extract server-side data for the row.
         var row = this;
         if (row.id in rowsData) {
           var data = rowsData[row.id];
           data.tableDrag = tableDrag;
 
-          // Create the row handler, make it accessible from the DOM row
-          // element.
           var rowHandler = new rowHandlers[data.rowHandler](row, data);
           $(row).data('fieldUIRowHandler', rowHandler);
         }
       });
     },
 
-    /**
-     * Event handler to be attached to form inputs triggering a region change.
-     */
-    onChange: function () {
+    onChange: function onChange() {
       var $trigger = $(this);
       var $row = $trigger.closest('tr');
       var rowHandler = $row.data('fieldUIRowHandler');
@@ -128,25 +75,19 @@
       var refreshRows = {};
       refreshRows[rowHandler.name] = $trigger.get(0);
 
-      // Handle region change.
       var region = rowHandler.getRegion();
       if (region !== rowHandler.region) {
-        // Remove parenting.
         $row.find('select.js-field-parent').val('');
-        // Let the row handler deal with the region change.
+
         $.extend(refreshRows, rowHandler.regionChange(region));
-        // Update the row region.
+
         rowHandler.region = region;
       }
 
-      // Ajax-update the rows.
       Drupal.fieldUIOverview.AJAXRefreshRows(refreshRows);
     },
 
-    /**
-     * Lets row handlers react when a row is dropped into a new region.
-     */
-    onDrop: function () {
+    onDrop: function onDrop() {
       var dragObject = this;
       var row = dragObject.rowObject.element;
       var $row = $(row);
@@ -156,63 +97,35 @@
         var region = regionRow.className.replace(/([^ ]+[ ]+)*region-([^ ]+)-message([ ]+[^ ]+)*/, '$2');
 
         if (region !== rowHandler.region) {
-          // Let the row handler deal with the region change.
           var refreshRows = rowHandler.regionChange(region);
-          // Update the row region.
+
           rowHandler.region = region;
-          // Ajax-update the rows.
+
           Drupal.fieldUIOverview.AJAXRefreshRows(refreshRows);
         }
       }
     },
 
-    /**
-     * Refreshes placeholder rows in empty regions while a row is being dragged.
-     *
-     * Copied from block.js.
-     *
-     * @param {HTMLElement} draggedRow
-     *   The tableDrag rowObject for the row being dragged.
-     */
-    onSwap: function (draggedRow) {
+    onSwap: function onSwap(draggedRow) {
       var rowObject = this;
       $(rowObject.table).find('tr.region-message').each(function () {
         var $this = $(this);
-        // If the dragged row is in this region, but above the message row, swap
-        // it down one space.
+
         if ($this.prev('tr').get(0) === rowObject.group[rowObject.group.length - 1]) {
-          // Prevent a recursion problem when using the keyboard to move rows
-          // up.
-          if ((rowObject.method !== 'keyboard' || rowObject.direction === 'down')) {
+          if (rowObject.method !== 'keyboard' || rowObject.direction === 'down') {
             rowObject.swap('after', this);
           }
         }
-        // This region has become empty.
+
         if ($this.next('tr').is(':not(.draggable)') || $this.next('tr').length === 0) {
           $this.removeClass('region-populated').addClass('region-empty');
-        }
-        // This region has become populated.
-        else if ($this.is('.region-empty')) {
-          $this.removeClass('region-empty').addClass('region-populated');
-        }
+        } else if ($this.is('.region-empty')) {
+            $this.removeClass('region-empty').addClass('region-populated');
+          }
       });
     },
 
-    /**
-     * Triggers Ajax refresh of selected rows.
-     *
-     * The 'format type' selects can trigger a series of changes in child rows.
-     * The #ajax behavior is therefore not attached directly to the selects, but
-     * triggered manually through a hidden #ajax 'Refresh' button.
-     *
-     * @param {object} rows
-     *   A hash object, whose keys are the names of the rows to refresh (they
-     *   will receive the 'ajax-new-content' effect on the server side), and
-     *   whose values are the DOM element in the row that should get an Ajax
-     *   throbber.
-     */
-    AJAXRefreshRows: function (rows) {
-      // Separate keys and values.
+    AJAXRefreshRows: function AJAXRefreshRows(rows) {
       var rowNames = [];
       var ajaxElements = [];
       var rowName;
@@ -224,42 +137,18 @@
       }
 
       if (rowNames.length) {
-        // Add a throbber next each of the ajaxElements.
         $(ajaxElements).after('<div class="ajax-progress ajax-progress-throbber"><div class="throbber">&nbsp;</div></div>');
 
-        // Fire the Ajax update.
         $('input[name=refresh_rows]').val(rowNames.join(' '));
         $('input[data-drupal-selector="edit-refresh"]').trigger('mousedown');
 
-        // Disabled elements do not appear in POST ajax data, so we mark the
-        // elements disabled only after firing the request.
         $(ajaxElements).prop('disabled', true);
       }
     }
   };
 
-  /**
-   * Row handlers for the 'Manage display' screen.
-   *
-   * @namespace
-   */
   Drupal.fieldUIDisplayOverview = {};
 
-  /**
-   * Constructor for a 'field' row handler.
-   *
-   * This handler is used for both fields and 'extra fields' rows.
-   *
-   * @constructor
-   *
-   * @param {HTMLTableRowElement} row
-   *   The row DOM element.
-   * @param {object} data
-   *   Additional data to be populated in the constructed object.
-   *
-   * @return {Drupal.fieldUIDisplayOverview.field}
-   *   The field row handler constructed.
-   */
   Drupal.fieldUIDisplayOverview.field = function (row, data) {
     this.row = row;
     this.name = data.name;
@@ -267,11 +156,9 @@
     this.tableDrag = data.tableDrag;
     this.defaultPlugin = data.defaultPlugin;
 
-    // Attach change listener to the 'plugin type' select.
     this.$pluginSelect = $(row).find('.field-plugin-type');
     this.$pluginSelect.on('change', Drupal.fieldUIOverview.onChange);
 
-    // Attach change listener to the 'region' select.
     this.$regionSelect = $(row).find('select.field-region');
     this.$regionSelect.on('change', Drupal.fieldUIOverview.onChange);
 
@@ -279,47 +166,16 @@
   };
 
   Drupal.fieldUIDisplayOverview.field.prototype = {
-
-    /**
-     * Returns the region corresponding to the current form values of the row.
-     *
-     * @return {string}
-     *   Either 'hidden' or 'content'.
-     */
-    getRegion: function () {
+    getRegion: function getRegion() {
       return this.$regionSelect.val();
     },
 
-    /**
-     * Reacts to a row being changed regions.
-     *
-     * This function is called when the row is moved to a different region, as
-     * a
-     * result of either :
-     * - a drag-and-drop action (the row's form elements then probably need to
-     * be updated accordingly)
-     * - user input in one of the form elements watched by the
-     *   {@link Drupal.fieldUIOverview.onChange} change listener.
-     *
-     * @param {string} region
-     *   The name of the new region for the row.
-     *
-     * @return {object}
-     *   A hash object indicating which rows should be Ajax-updated as a result
-     *   of the change, in the format expected by
-     *   {@link Drupal.fieldUIOverview.AJAXRefreshRows}.
-     */
-    regionChange: function (region) {
-      // Replace dashes with underscores.
+    regionChange: function regionChange(region) {
       region = region.replace(/-/g, '_');
 
-      // Set the region of the select list.
       this.$regionSelect.val(region);
 
-      // Restore the formatter back to the default formatter. Pseudo-fields
-      // do not have default formatters, we just return to 'visible' for
-      // those.
-      var value = (typeof this.defaultPlugin !== 'undefined') ? this.defaultPlugin : this.$pluginSelect.find('option').val();
+      var value = typeof this.defaultPlugin !== 'undefined' ? this.defaultPlugin : this.$pluginSelect.find('option').val();
 
       if (typeof value !== 'undefined') {
         this.$pluginSelect.val(value);
@@ -331,5 +187,4 @@
       return refreshRows;
     }
   };
-
-})(jQuery, Drupal, drupalSettings);
+})(jQuery, Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/modules/file/file.es6.js b/core/modules/file/file.es6.js
new file mode 100644
index 000000000000..8ed377eec3d8
--- /dev/null
+++ b/core/modules/file/file.es6.js
@@ -0,0 +1,257 @@
+/**
+ * @file
+ * Provides JavaScript additions to the managed file field type.
+ *
+ * This file provides progress bar support (if available), popup windows for
+ * file previews, and disabling of other file fields during Ajax uploads (which
+ * prevents separate file fields from accidentally uploading files).
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * Attach behaviors to the file fields passed in the settings.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches validation for file extensions.
+   * @prop {Drupal~behaviorDetach} detach
+   *   Detaches validation for file extensions.
+   */
+  Drupal.behaviors.fileValidateAutoAttach = {
+    attach: function (context, settings) {
+      var $context = $(context);
+      var elements;
+
+      function initFileValidation(selector) {
+        $context.find(selector)
+          .once('fileValidate')
+          .on('change.fileValidate', {extensions: elements[selector]}, Drupal.file.validateExtension);
+      }
+
+      if (settings.file && settings.file.elements) {
+        elements = settings.file.elements;
+        Object.keys(elements).forEach(initFileValidation);
+      }
+    },
+    detach: function (context, settings, trigger) {
+      var $context = $(context);
+      var elements;
+
+      function removeFileValidation(selector) {
+        $context.find(selector)
+          .removeOnce('fileValidate')
+          .off('change.fileValidate', Drupal.file.validateExtension);
+      }
+
+      if (trigger === 'unload' && settings.file && settings.file.elements) {
+        elements = settings.file.elements;
+        Object.keys(elements).forEach(removeFileValidation);
+      }
+    }
+  };
+
+  /**
+   * Attach behaviors to file element auto upload.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches triggers for the upload button.
+   * @prop {Drupal~behaviorDetach} detach
+   *   Detaches auto file upload trigger.
+   */
+  Drupal.behaviors.fileAutoUpload = {
+    attach: function (context) {
+      $(context).find('input[type="file"]').once('auto-file-upload').on('change.autoFileUpload', Drupal.file.triggerUploadButton);
+    },
+    detach: function (context, setting, trigger) {
+      if (trigger === 'unload') {
+        $(context).find('input[type="file"]').removeOnce('auto-file-upload').off('.autoFileUpload');
+      }
+    }
+  };
+
+  /**
+   * Attach behaviors to the file upload and remove buttons.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches form submit events.
+   * @prop {Drupal~behaviorDetach} detach
+   *   Detaches form submit events.
+   */
+  Drupal.behaviors.fileButtons = {
+    attach: function (context) {
+      var $context = $(context);
+      $context.find('.js-form-submit').on('mousedown', Drupal.file.disableFields);
+      $context.find('.js-form-managed-file .js-form-submit').on('mousedown', Drupal.file.progressBar);
+    },
+    detach: function (context) {
+      var $context = $(context);
+      $context.find('.js-form-submit').off('mousedown', Drupal.file.disableFields);
+      $context.find('.js-form-managed-file .js-form-submit').off('mousedown', Drupal.file.progressBar);
+    }
+  };
+
+  /**
+   * Attach behaviors to links within managed file elements for preview windows.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches triggers.
+   * @prop {Drupal~behaviorDetach} detach
+   *   Detaches triggers.
+   */
+  Drupal.behaviors.filePreviewLinks = {
+    attach: function (context) {
+      $(context).find('div.js-form-managed-file .file a').on('click', Drupal.file.openInNewWindow);
+    },
+    detach: function (context) {
+      $(context).find('div.js-form-managed-file .file a').off('click', Drupal.file.openInNewWindow);
+    }
+  };
+
+  /**
+   * File upload utility functions.
+   *
+   * @namespace
+   */
+  Drupal.file = Drupal.file || {
+
+    /**
+     * Client-side file input validation of file extensions.
+     *
+     * @name Drupal.file.validateExtension
+     *
+     * @param {jQuery.Event} event
+     *   The event triggered. For example `change.fileValidate`.
+     */
+    validateExtension: function (event) {
+      event.preventDefault();
+      // Remove any previous errors.
+      $('.file-upload-js-error').remove();
+
+      // Add client side validation for the input[type=file].
+      var extensionPattern = event.data.extensions.replace(/,\s*/g, '|');
+      if (extensionPattern.length > 1 && this.value.length > 0) {
+        var acceptableMatch = new RegExp('\\.(' + extensionPattern + ')$', 'gi');
+        if (!acceptableMatch.test(this.value)) {
+          var error = Drupal.t('The selected file %filename cannot be uploaded. Only files with the following extensions are allowed: %extensions.', {
+            // According to the specifications of HTML5, a file upload control
+            // should not reveal the real local path to the file that a user
+            // has selected. Some web browsers implement this restriction by
+            // replacing the local path with "C:\fakepath\", which can cause
+            // confusion by leaving the user thinking perhaps Drupal could not
+            // find the file because it messed up the file path. To avoid this
+            // confusion, therefore, we strip out the bogus fakepath string.
+            '%filename': this.value.replace('C:\\fakepath\\', ''),
+            '%extensions': extensionPattern.replace(/\|/g, ', ')
+          });
+          $(this).closest('div.js-form-managed-file').prepend('<div class="messages messages--error file-upload-js-error" aria-live="polite">' + error + '</div>');
+          this.value = '';
+          // Cancel all other change event handlers.
+          event.stopImmediatePropagation();
+        }
+      }
+    },
+
+    /**
+     * Trigger the upload_button mouse event to auto-upload as a managed file.
+     *
+     * @name Drupal.file.triggerUploadButton
+     *
+     * @param {jQuery.Event} event
+     *   The event triggered. For example `change.autoFileUpload`.
+     */
+    triggerUploadButton: function (event) {
+      $(event.target).closest('.js-form-managed-file').find('.js-form-submit').trigger('mousedown');
+    },
+
+    /**
+     * Prevent file uploads when using buttons not intended to upload.
+     *
+     * @name Drupal.file.disableFields
+     *
+     * @param {jQuery.Event} event
+     *   The event triggered, most likely a `mousedown` event.
+     */
+    disableFields: function (event) {
+      var $clickedButton = $(this).findOnce('ajax');
+
+      // Only disable upload fields for Ajax buttons.
+      if (!$clickedButton.length) {
+        return;
+      }
+
+      // Check if we're working with an "Upload" button.
+      var $enabledFields = [];
+      if ($clickedButton.closest('div.js-form-managed-file').length > 0) {
+        $enabledFields = $clickedButton.closest('div.js-form-managed-file').find('input.js-form-file');
+      }
+
+      // Temporarily disable upload fields other than the one we're currently
+      // working with. Filter out fields that are already disabled so that they
+      // do not get enabled when we re-enable these fields at the end of
+      // behavior processing. Re-enable in a setTimeout set to a relatively
+      // short amount of time (1 second). All the other mousedown handlers
+      // (like Drupal's Ajax behaviors) are executed before any timeout
+      // functions are called, so we don't have to worry about the fields being
+      // re-enabled too soon. @todo If the previous sentence is true, why not
+      // set the timeout to 0?
+      var $fieldsToTemporarilyDisable = $('div.js-form-managed-file input.js-form-file').not($enabledFields).not(':disabled');
+      $fieldsToTemporarilyDisable.prop('disabled', true);
+      setTimeout(function () {
+        $fieldsToTemporarilyDisable.prop('disabled', false);
+      }, 1000);
+    },
+
+    /**
+     * Add progress bar support if possible.
+     *
+     * @name Drupal.file.progressBar
+     *
+     * @param {jQuery.Event} event
+     *   The event triggered, most likely a `mousedown` event.
+     */
+    progressBar: function (event) {
+      var $clickedButton = $(this);
+      var $progressId = $clickedButton.closest('div.js-form-managed-file').find('input.file-progress');
+      if ($progressId.length) {
+        var originalName = $progressId.attr('name');
+
+        // Replace the name with the required identifier.
+        $progressId.attr('name', originalName.match(/APC_UPLOAD_PROGRESS|UPLOAD_IDENTIFIER/)[0]);
+
+        // Restore the original name after the upload begins.
+        setTimeout(function () {
+          $progressId.attr('name', originalName);
+        }, 1000);
+      }
+      // Show the progress bar if the upload takes longer than half a second.
+      setTimeout(function () {
+        $clickedButton.closest('div.js-form-managed-file').find('div.ajax-progress-bar').slideDown();
+      }, 500);
+    },
+
+    /**
+     * Open links to files within forms in a new window.
+     *
+     * @name Drupal.file.openInNewWindow
+     *
+     * @param {jQuery.Event} event
+     *   The event triggered, most likely a `click` event.
+     */
+    openInNewWindow: function (event) {
+      event.preventDefault();
+      $(this).attr('target', '_blank');
+      window.open(this.href, 'filePreview', 'toolbar=0,scrollbars=1,location=1,statusbar=1,menubar=0,resizable=1,width=500,height=550');
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/core/modules/file/file.js b/core/modules/file/file.js
index 8ed377eec3d8..c65709a7ef6a 100644
--- a/core/modules/file/file.js
+++ b/core/modules/file/file.js
@@ -1,35 +1,22 @@
 /**
- * @file
- * Provides JavaScript additions to the managed file field type.
- *
- * This file provides progress bar support (if available), popup windows for
- * file previews, and disabling of other file fields during Ajax uploads (which
- * prevents separate file fields from accidentally uploading files).
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/file/file.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   * Attach behaviors to the file fields passed in the settings.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches validation for file extensions.
-   * @prop {Drupal~behaviorDetach} detach
-   *   Detaches validation for file extensions.
-   */
   Drupal.behaviors.fileValidateAutoAttach = {
-    attach: function (context, settings) {
+    attach: function attach(context, settings) {
       var $context = $(context);
       var elements;
 
       function initFileValidation(selector) {
-        $context.find(selector)
-          .once('fileValidate')
-          .on('change.fileValidate', {extensions: elements[selector]}, Drupal.file.validateExtension);
+        $context.find(selector).once('fileValidate').on('change.fileValidate', { extensions: elements[selector] }, Drupal.file.validateExtension);
       }
 
       if (settings.file && settings.file.elements) {
@@ -37,14 +24,12 @@
         Object.keys(elements).forEach(initFileValidation);
       }
     },
-    detach: function (context, settings, trigger) {
+    detach: function detach(context, settings, trigger) {
       var $context = $(context);
       var elements;
 
       function removeFileValidation(selector) {
-        $context.find(selector)
-          .removeOnce('fileValidate')
-          .off('change.fileValidate', Drupal.file.validateExtension);
+        $context.find(selector).removeOnce('fileValidate').off('change.fileValidate', Drupal.file.validateExtension);
       }
 
       if (trigger === 'unload' && settings.file && settings.file.elements) {
@@ -54,156 +39,77 @@
     }
   };
 
-  /**
-   * Attach behaviors to file element auto upload.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches triggers for the upload button.
-   * @prop {Drupal~behaviorDetach} detach
-   *   Detaches auto file upload trigger.
-   */
   Drupal.behaviors.fileAutoUpload = {
-    attach: function (context) {
+    attach: function attach(context) {
       $(context).find('input[type="file"]').once('auto-file-upload').on('change.autoFileUpload', Drupal.file.triggerUploadButton);
     },
-    detach: function (context, setting, trigger) {
+    detach: function detach(context, setting, trigger) {
       if (trigger === 'unload') {
         $(context).find('input[type="file"]').removeOnce('auto-file-upload').off('.autoFileUpload');
       }
     }
   };
 
-  /**
-   * Attach behaviors to the file upload and remove buttons.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches form submit events.
-   * @prop {Drupal~behaviorDetach} detach
-   *   Detaches form submit events.
-   */
   Drupal.behaviors.fileButtons = {
-    attach: function (context) {
+    attach: function attach(context) {
       var $context = $(context);
       $context.find('.js-form-submit').on('mousedown', Drupal.file.disableFields);
       $context.find('.js-form-managed-file .js-form-submit').on('mousedown', Drupal.file.progressBar);
     },
-    detach: function (context) {
+    detach: function detach(context) {
       var $context = $(context);
       $context.find('.js-form-submit').off('mousedown', Drupal.file.disableFields);
       $context.find('.js-form-managed-file .js-form-submit').off('mousedown', Drupal.file.progressBar);
     }
   };
 
-  /**
-   * Attach behaviors to links within managed file elements for preview windows.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches triggers.
-   * @prop {Drupal~behaviorDetach} detach
-   *   Detaches triggers.
-   */
   Drupal.behaviors.filePreviewLinks = {
-    attach: function (context) {
+    attach: function attach(context) {
       $(context).find('div.js-form-managed-file .file a').on('click', Drupal.file.openInNewWindow);
     },
-    detach: function (context) {
+    detach: function detach(context) {
       $(context).find('div.js-form-managed-file .file a').off('click', Drupal.file.openInNewWindow);
     }
   };
 
-  /**
-   * File upload utility functions.
-   *
-   * @namespace
-   */
   Drupal.file = Drupal.file || {
-
-    /**
-     * Client-side file input validation of file extensions.
-     *
-     * @name Drupal.file.validateExtension
-     *
-     * @param {jQuery.Event} event
-     *   The event triggered. For example `change.fileValidate`.
-     */
-    validateExtension: function (event) {
+    validateExtension: function validateExtension(event) {
       event.preventDefault();
-      // Remove any previous errors.
+
       $('.file-upload-js-error').remove();
 
-      // Add client side validation for the input[type=file].
       var extensionPattern = event.data.extensions.replace(/,\s*/g, '|');
       if (extensionPattern.length > 1 && this.value.length > 0) {
         var acceptableMatch = new RegExp('\\.(' + extensionPattern + ')$', 'gi');
         if (!acceptableMatch.test(this.value)) {
           var error = Drupal.t('The selected file %filename cannot be uploaded. Only files with the following extensions are allowed: %extensions.', {
-            // According to the specifications of HTML5, a file upload control
-            // should not reveal the real local path to the file that a user
-            // has selected. Some web browsers implement this restriction by
-            // replacing the local path with "C:\fakepath\", which can cause
-            // confusion by leaving the user thinking perhaps Drupal could not
-            // find the file because it messed up the file path. To avoid this
-            // confusion, therefore, we strip out the bogus fakepath string.
             '%filename': this.value.replace('C:\\fakepath\\', ''),
             '%extensions': extensionPattern.replace(/\|/g, ', ')
           });
           $(this).closest('div.js-form-managed-file').prepend('<div class="messages messages--error file-upload-js-error" aria-live="polite">' + error + '</div>');
           this.value = '';
-          // Cancel all other change event handlers.
+
           event.stopImmediatePropagation();
         }
       }
     },
 
-    /**
-     * Trigger the upload_button mouse event to auto-upload as a managed file.
-     *
-     * @name Drupal.file.triggerUploadButton
-     *
-     * @param {jQuery.Event} event
-     *   The event triggered. For example `change.autoFileUpload`.
-     */
-    triggerUploadButton: function (event) {
+    triggerUploadButton: function triggerUploadButton(event) {
       $(event.target).closest('.js-form-managed-file').find('.js-form-submit').trigger('mousedown');
     },
 
-    /**
-     * Prevent file uploads when using buttons not intended to upload.
-     *
-     * @name Drupal.file.disableFields
-     *
-     * @param {jQuery.Event} event
-     *   The event triggered, most likely a `mousedown` event.
-     */
-    disableFields: function (event) {
+    disableFields: function disableFields(event) {
       var $clickedButton = $(this).findOnce('ajax');
 
-      // Only disable upload fields for Ajax buttons.
       if (!$clickedButton.length) {
         return;
       }
 
-      // Check if we're working with an "Upload" button.
       var $enabledFields = [];
       if ($clickedButton.closest('div.js-form-managed-file').length > 0) {
         $enabledFields = $clickedButton.closest('div.js-form-managed-file').find('input.js-form-file');
       }
 
-      // Temporarily disable upload fields other than the one we're currently
-      // working with. Filter out fields that are already disabled so that they
-      // do not get enabled when we re-enable these fields at the end of
-      // behavior processing. Re-enable in a setTimeout set to a relatively
-      // short amount of time (1 second). All the other mousedown handlers
-      // (like Drupal's Ajax behaviors) are executed before any timeout
-      // functions are called, so we don't have to worry about the fields being
-      // re-enabled too soon. @todo If the previous sentence is true, why not
-      // set the timeout to 0?
       var $fieldsToTemporarilyDisable = $('div.js-form-managed-file input.js-form-file').not($enabledFields).not(':disabled');
       $fieldsToTemporarilyDisable.prop('disabled', true);
       setTimeout(function () {
@@ -211,47 +117,28 @@
       }, 1000);
     },
 
-    /**
-     * Add progress bar support if possible.
-     *
-     * @name Drupal.file.progressBar
-     *
-     * @param {jQuery.Event} event
-     *   The event triggered, most likely a `mousedown` event.
-     */
-    progressBar: function (event) {
+    progressBar: function progressBar(event) {
       var $clickedButton = $(this);
       var $progressId = $clickedButton.closest('div.js-form-managed-file').find('input.file-progress');
       if ($progressId.length) {
         var originalName = $progressId.attr('name');
 
-        // Replace the name with the required identifier.
         $progressId.attr('name', originalName.match(/APC_UPLOAD_PROGRESS|UPLOAD_IDENTIFIER/)[0]);
 
-        // Restore the original name after the upload begins.
         setTimeout(function () {
           $progressId.attr('name', originalName);
         }, 1000);
       }
-      // Show the progress bar if the upload takes longer than half a second.
+
       setTimeout(function () {
         $clickedButton.closest('div.js-form-managed-file').find('div.ajax-progress-bar').slideDown();
       }, 500);
     },
 
-    /**
-     * Open links to files within forms in a new window.
-     *
-     * @name Drupal.file.openInNewWindow
-     *
-     * @param {jQuery.Event} event
-     *   The event triggered, most likely a `click` event.
-     */
-    openInNewWindow: function (event) {
+    openInNewWindow: function openInNewWindow(event) {
       event.preventDefault();
       $(this).attr('target', '_blank');
       window.open(this.href, 'filePreview', 'toolbar=0,scrollbars=1,location=1,statusbar=1,menubar=0,resizable=1,width=500,height=550');
     }
   };
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/modules/filter/filter.admin.es6.js b/core/modules/filter/filter.admin.es6.js
new file mode 100644
index 000000000000..31e0582c1899
--- /dev/null
+++ b/core/modules/filter/filter.admin.es6.js
@@ -0,0 +1,69 @@
+/**
+ * @file
+ * Attaches administration-specific behavior for the Filter module.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * Displays and updates the status of filters on the admin page.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches behaviors to the filter admin view.
+   */
+  Drupal.behaviors.filterStatus = {
+    attach: function (context, settings) {
+      var $context = $(context);
+      $context.find('#filters-status-wrapper input.form-checkbox').once('filter-status').each(function () {
+        var $checkbox = $(this);
+        // Retrieve the tabledrag row belonging to this filter.
+        var $row = $context.find('#' + $checkbox.attr('id').replace(/-status$/, '-weight')).closest('tr');
+        // Retrieve the vertical tab belonging to this filter.
+        var $filterSettings = $context.find('#' + $checkbox.attr('id').replace(/-status$/, '-settings'));
+        var filterSettingsTab = $filterSettings.data('verticalTab');
+
+        // Bind click handler to this checkbox to conditionally show and hide
+        // the filter's tableDrag row and vertical tab pane.
+        $checkbox.on('click.filterUpdate', function () {
+          if ($checkbox.is(':checked')) {
+            $row.show();
+            if (filterSettingsTab) {
+              filterSettingsTab.tabShow().updateSummary();
+            }
+            else {
+              // On very narrow viewports, Vertical Tabs are disabled.
+              $filterSettings.show();
+            }
+          }
+          else {
+            $row.hide();
+            if (filterSettingsTab) {
+              filterSettingsTab.tabHide().updateSummary();
+            }
+            else {
+              // On very narrow viewports, Vertical Tabs are disabled.
+              $filterSettings.hide();
+            }
+          }
+          // Restripe table after toggling visibility of table row.
+          Drupal.tableDrag['filter-order'].restripeTable();
+        });
+
+        // Attach summary for configurable filters (only for screen readers).
+        if (filterSettingsTab) {
+          filterSettingsTab.details.drupalSetSummary(function (tabContext) {
+            return $checkbox.is(':checked') ? Drupal.t('Enabled') : Drupal.t('Disabled');
+          });
+        }
+
+        // Trigger our bound click handler to update elements to initial state.
+        $checkbox.triggerHandler('click.filterUpdate');
+      });
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/core/modules/filter/filter.admin.js b/core/modules/filter/filter.admin.js
index 31e0582c1899..8fb815e01b03 100644
--- a/core/modules/filter/filter.admin.js
+++ b/core/modules/filter/filter.admin.js
@@ -1,69 +1,54 @@
 /**
- * @file
- * Attaches administration-specific behavior for the Filter module.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/filter/filter.admin.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   * Displays and updates the status of filters on the admin page.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches behaviors to the filter admin view.
-   */
   Drupal.behaviors.filterStatus = {
-    attach: function (context, settings) {
+    attach: function attach(context, settings) {
       var $context = $(context);
       $context.find('#filters-status-wrapper input.form-checkbox').once('filter-status').each(function () {
         var $checkbox = $(this);
-        // Retrieve the tabledrag row belonging to this filter.
+
         var $row = $context.find('#' + $checkbox.attr('id').replace(/-status$/, '-weight')).closest('tr');
-        // Retrieve the vertical tab belonging to this filter.
+
         var $filterSettings = $context.find('#' + $checkbox.attr('id').replace(/-status$/, '-settings'));
         var filterSettingsTab = $filterSettings.data('verticalTab');
 
-        // Bind click handler to this checkbox to conditionally show and hide
-        // the filter's tableDrag row and vertical tab pane.
         $checkbox.on('click.filterUpdate', function () {
           if ($checkbox.is(':checked')) {
             $row.show();
             if (filterSettingsTab) {
               filterSettingsTab.tabShow().updateSummary();
-            }
-            else {
-              // On very narrow viewports, Vertical Tabs are disabled.
+            } else {
               $filterSettings.show();
             }
-          }
-          else {
+          } else {
             $row.hide();
             if (filterSettingsTab) {
               filterSettingsTab.tabHide().updateSummary();
-            }
-            else {
-              // On very narrow viewports, Vertical Tabs are disabled.
+            } else {
               $filterSettings.hide();
             }
           }
-          // Restripe table after toggling visibility of table row.
+
           Drupal.tableDrag['filter-order'].restripeTable();
         });
 
-        // Attach summary for configurable filters (only for screen readers).
         if (filterSettingsTab) {
           filterSettingsTab.details.drupalSetSummary(function (tabContext) {
             return $checkbox.is(':checked') ? Drupal.t('Enabled') : Drupal.t('Disabled');
           });
         }
 
-        // Trigger our bound click handler to update elements to initial state.
         $checkbox.triggerHandler('click.filterUpdate');
       });
     }
   };
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/modules/filter/filter.es6.js b/core/modules/filter/filter.es6.js
new file mode 100644
index 000000000000..b79104733edc
--- /dev/null
+++ b/core/modules/filter/filter.es6.js
@@ -0,0 +1,39 @@
+/**
+ * @file
+ * Attaches behavior for the Filter module.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * Displays the guidelines of the selected text format automatically.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches behavior for updating filter guidelines.
+   */
+  Drupal.behaviors.filterGuidelines = {
+    attach: function (context) {
+
+      function updateFilterGuidelines(event) {
+        var $this = $(event.target);
+        var value = $this.val();
+        $this.closest('.filter-wrapper')
+          .find('.filter-guidelines-item').hide()
+          .filter('.filter-guidelines-' + value).show();
+      }
+
+      $(context).find('.filter-guidelines').once('filter-guidelines')
+        .find(':header').hide()
+        .closest('.filter-wrapper').find('select.filter-list')
+        .on('change.filterGuidelines', updateFilterGuidelines)
+        // Need to trigger the namespaced event to avoid triggering formUpdated
+        // when initializing the select.
+        .trigger('change.filterGuidelines');
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/core/modules/filter/filter.filter_html.admin.es6.js b/core/modules/filter/filter.filter_html.admin.es6.js
new file mode 100644
index 000000000000..7ddca90bba84
--- /dev/null
+++ b/core/modules/filter/filter.filter_html.admin.es6.js
@@ -0,0 +1,328 @@
+/**
+ * @file
+ * Attaches behavior for updating filter_html's settings automatically.
+ */
+
+(function ($, Drupal, _, document) {
+
+  'use strict';
+
+  if (Drupal.filterConfiguration) {
+
+    /**
+     * Implement a live setting parser to prevent text editors from automatically
+     * enabling buttons that are not allowed by this filter's configuration.
+     *
+     * @namespace
+     */
+    Drupal.filterConfiguration.liveSettingParsers.filter_html = {
+
+      /**
+       * @return {Array}
+       *   An array of filter rules.
+       */
+      getRules: function () {
+        var currentValue = $('#edit-filters-filter-html-settings-allowed-html').val();
+        var rules = Drupal.behaviors.filterFilterHtmlUpdating._parseSetting(currentValue);
+
+        // Build a FilterHTMLRule that reflects the hard-coded behavior that
+        // strips all "style" attribute and all "on*" attributes.
+        var rule = new Drupal.FilterHTMLRule();
+        rule.restrictedTags.tags = ['*'];
+        rule.restrictedTags.forbidden.attributes = ['style', 'on*'];
+        rules.push(rule);
+
+        return rules;
+      }
+    };
+  }
+
+  /**
+   * Displays and updates what HTML tags are allowed to use in a filter.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @todo Remove everything but 'attach' and 'detach' and make a proper object.
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches behavior for updating allowed HTML tags.
+   */
+  Drupal.behaviors.filterFilterHtmlUpdating = {
+
+    // The form item contains the "Allowed HTML tags" setting.
+    $allowedHTMLFormItem: null,
+
+    // The description for the "Allowed HTML tags" field.
+    $allowedHTMLDescription: null,
+
+    /**
+     * The parsed, user-entered tag list of $allowedHTMLFormItem
+     *
+     * @var {Object.<string, Drupal.FilterHTMLRule>}
+     */
+    userTags: {},
+
+    // The auto-created tag list thus far added.
+    autoTags: null,
+
+    // Track which new features have been added to the text editor.
+    newFeatures: {},
+
+    attach: function (context, settings) {
+      var that = this;
+      $(context).find('[name="filters[filter_html][settings][allowed_html]"]').once('filter-filter_html-updating').each(function () {
+        that.$allowedHTMLFormItem = $(this);
+        that.$allowedHTMLDescription = that.$allowedHTMLFormItem.closest('.js-form-item').find('.description');
+        that.userTags = that._parseSetting(this.value);
+
+        // Update the new allowed tags based on added text editor features.
+        $(document)
+          .on('drupalEditorFeatureAdded', function (e, feature) {
+            that.newFeatures[feature.name] = feature.rules;
+            that._updateAllowedTags();
+          })
+          .on('drupalEditorFeatureModified', function (e, feature) {
+            if (that.newFeatures.hasOwnProperty(feature.name)) {
+              that.newFeatures[feature.name] = feature.rules;
+              that._updateAllowedTags();
+            }
+          })
+          .on('drupalEditorFeatureRemoved', function (e, feature) {
+            if (that.newFeatures.hasOwnProperty(feature.name)) {
+              delete that.newFeatures[feature.name];
+              that._updateAllowedTags();
+            }
+          });
+
+        // When the allowed tags list is manually changed, update userTags.
+        that.$allowedHTMLFormItem.on('change.updateUserTags', function () {
+          that.userTags = _.difference(that._parseSetting(this.value), that.autoTags);
+        });
+      });
+    },
+
+    /**
+     * Updates the "Allowed HTML tags" setting and shows an informative message.
+     */
+    _updateAllowedTags: function () {
+      // Update the list of auto-created tags.
+      this.autoTags = this._calculateAutoAllowedTags(this.userTags, this.newFeatures);
+
+      // Remove any previous auto-created tag message.
+      this.$allowedHTMLDescription.find('.editor-update-message').remove();
+
+      // If any auto-created tags: insert message and update form item.
+      if (!_.isEmpty(this.autoTags)) {
+        this.$allowedHTMLDescription.append(Drupal.theme('filterFilterHTMLUpdateMessage', this.autoTags));
+        var userTagsWithoutOverrides = _.omit(this.userTags, _.keys(this.autoTags));
+        this.$allowedHTMLFormItem.val(this._generateSetting(userTagsWithoutOverrides) + ' ' + this._generateSetting(this.autoTags));
+      }
+      // Restore to original state.
+      else {
+        this.$allowedHTMLFormItem.val(this._generateSetting(this.userTags));
+      }
+    },
+
+    /**
+     * Calculates which HTML tags the added text editor buttons need to work.
+     *
+     * The filter_html filter is only concerned with the required tags, not with
+     * any properties, nor with each feature's "allowed" tags.
+     *
+     * @param {Array} userAllowedTags
+     *   The list of user-defined allowed tags.
+     * @param {object} newFeatures
+     *   A list of {@link Drupal.EditorFeature} objects' rules, keyed by
+     *   their name.
+     *
+     * @return {Array}
+     *   A list of new allowed tags.
+     */
+    _calculateAutoAllowedTags: function (userAllowedTags, newFeatures) {
+      var featureName;
+      var feature;
+      var featureRule;
+      var filterRule;
+      var tag;
+      var editorRequiredTags = {};
+      // Map the newly added Text Editor features to Drupal.FilterHtmlRule
+      // objects (to allow comparing userTags with autoTags).
+      for (featureName in newFeatures) {
+        if (newFeatures.hasOwnProperty(featureName)) {
+          feature = newFeatures[featureName];
+          for (var f = 0; f < feature.length; f++) {
+            featureRule = feature[f];
+            for (var t = 0; t < featureRule.required.tags.length; t++) {
+              tag = featureRule.required.tags[t];
+              if (!_.has(editorRequiredTags, tag)) {
+                filterRule = new Drupal.FilterHTMLRule();
+                filterRule.restrictedTags.tags = [tag];
+                // @todo Neither Drupal.FilterHtmlRule nor
+                //   Drupal.EditorFeatureHTMLRule allow for generic attribute
+                //   value restrictions, only for the "class" and "style"
+                //   attribute's values to be restricted. The filter_html filter
+                //   always disallows the "style" attribute, so we only need to
+                //   support "class" attribute value restrictions. Fix once
+                //   https://www.drupal.org/node/2567801 lands.
+                filterRule.restrictedTags.allowed.attributes = featureRule.required.attributes.slice(0);
+                filterRule.restrictedTags.allowed.classes = featureRule.required.classes.slice(0);
+                editorRequiredTags[tag] = filterRule;
+              }
+              // The tag is already allowed, add any additionally allowed
+              // attributes.
+              else {
+                filterRule = editorRequiredTags[tag];
+                filterRule.restrictedTags.allowed.attributes = _.union(filterRule.restrictedTags.allowed.attributes, featureRule.required.attributes);
+                filterRule.restrictedTags.allowed.classes = _.union(filterRule.restrictedTags.allowed.classes, featureRule.required.classes);
+              }
+            }
+          }
+        }
+      }
+
+      // Now compare userAllowedTags with editorRequiredTags, and build
+      // autoAllowedTags, which contains:
+      // - any tags in editorRequiredTags but not in userAllowedTags (i.e. tags
+      //   that are additionally going to be allowed)
+      // - any tags in editorRequiredTags that already exists in userAllowedTags
+      //   but does not allow all attributes or attribute values
+      var autoAllowedTags = {};
+      for (tag in editorRequiredTags) {
+        // If userAllowedTags does not contain a rule for this editor-required
+        // tag, then add it to the list of automatically allowed tags.
+        if (!_.has(userAllowedTags, tag)) {
+          autoAllowedTags[tag] = editorRequiredTags[tag];
+        }
+        // Otherwise, if userAllowedTags already allows this tag, then check if
+        // additional attributes and classes on this tag are required by the
+        // editor.
+        else {
+          var requiredAttributes = editorRequiredTags[tag].restrictedTags.allowed.attributes;
+          var allowedAttributes = userAllowedTags[tag].restrictedTags.allowed.attributes;
+          var needsAdditionalAttributes = requiredAttributes.length && _.difference(requiredAttributes, allowedAttributes).length;
+          var requiredClasses = editorRequiredTags[tag].restrictedTags.allowed.classes;
+          var allowedClasses = userAllowedTags[tag].restrictedTags.allowed.classes;
+          var needsAdditionalClasses = requiredClasses.length && _.difference(requiredClasses, allowedClasses).length;
+          if (needsAdditionalAttributes || needsAdditionalClasses) {
+            autoAllowedTags[tag] = userAllowedTags[tag].clone();
+          }
+          if (needsAdditionalAttributes) {
+            autoAllowedTags[tag].restrictedTags.allowed.attributes = _.union(allowedAttributes, requiredAttributes);
+          }
+          if (needsAdditionalClasses) {
+            autoAllowedTags[tag].restrictedTags.allowed.classes = _.union(allowedClasses, requiredClasses);
+          }
+        }
+      }
+
+      return autoAllowedTags;
+    },
+
+    /**
+     * Parses the value of this.$allowedHTMLFormItem.
+     *
+     * @param {string} setting
+     *   The string representation of the setting. For example:
+     *     <p class="callout"> <br> <a href hreflang>
+     *
+     * @return {Object.<string, Drupal.FilterHTMLRule>}
+     *   The corresponding text filter HTML rule objects, one per tag, keyed by
+     *   tag name.
+     */
+    _parseSetting: function (setting) {
+      var node;
+      var tag;
+      var rule;
+      var attributes;
+      var attribute;
+      var allowedTags = setting.match(/(<[^>]+>)/g);
+      var sandbox = document.createElement('div');
+      var rules = {};
+      for (var t = 0; t < allowedTags.length; t++) {
+        // Let the browser do the parsing work for us.
+        sandbox.innerHTML = allowedTags[t];
+        node = sandbox.firstChild;
+        tag = node.tagName.toLowerCase();
+
+        // Build the Drupal.FilterHtmlRule object.
+        rule = new Drupal.FilterHTMLRule();
+        // We create one rule per allowed tag, so always one tag.
+        rule.restrictedTags.tags = [tag];
+        // Add the attribute restrictions.
+        attributes = node.attributes;
+        for (var i = 0; i < attributes.length; i++) {
+          attribute = attributes.item(i);
+          var attributeName = attribute.nodeName;
+          // @todo Drupal.FilterHtmlRule does not allow for generic attribute
+          //   value restrictions, only for the "class" and "style" attribute's
+          //   values. The filter_html filter always disallows the "style"
+          //   attribute, so we only need to support "class" attribute value
+          //   restrictions. Fix once https://www.drupal.org/node/2567801 lands.
+          if (attributeName === 'class') {
+            var attributeValue = attribute.textContent;
+            rule.restrictedTags.allowed.classes = attributeValue.split(' ');
+          }
+          else {
+            rule.restrictedTags.allowed.attributes.push(attributeName);
+          }
+        }
+
+        rules[tag] = rule;
+      }
+      return rules;
+    },
+
+    /**
+     * Generates the value of this.$allowedHTMLFormItem.
+     *
+     * @param {Object.<string, Drupal.FilterHTMLRule>} tags
+     *   The parsed representation of the setting.
+     *
+     * @return {Array}
+     *   The string representation of the setting. e.g. "<p> <br> <a>"
+     */
+    _generateSetting: function (tags) {
+      return _.reduce(tags, function (setting, rule, tag) {
+        if (setting.length) {
+          setting += ' ';
+        }
+
+        setting += '<' + tag;
+        if (rule.restrictedTags.allowed.attributes.length) {
+          setting += ' ' + rule.restrictedTags.allowed.attributes.join(' ');
+        }
+        // @todo Drupal.FilterHtmlRule does not allow for generic attribute
+        //   value restrictions, only for the "class" and "style" attribute's
+        //   values. The filter_html filter always disallows the "style"
+        //   attribute, so we only need to support "class" attribute value
+        //   restrictions. Fix once https://www.drupal.org/node/2567801 lands.
+        if (rule.restrictedTags.allowed.classes.length) {
+          setting += ' class="' + rule.restrictedTags.allowed.classes.join(' ') + '"';
+        }
+
+        setting += '>';
+        return setting;
+      }, '');
+    }
+
+  };
+
+  /**
+   * Theme function for the filter_html update message.
+   *
+   * @param {Array} tags
+   *   An array of the new tags that are to be allowed.
+   *
+   * @return {string}
+   *   The corresponding HTML.
+   */
+  Drupal.theme.filterFilterHTMLUpdateMessage = function (tags) {
+    var html = '';
+    var tagList = Drupal.behaviors.filterFilterHtmlUpdating._generateSetting(tags);
+    html += '<p class="editor-update-message">';
+    html += Drupal.t('Based on the text editor configuration, these tags have automatically been added: <strong>@tag-list</strong>.', {'@tag-list': tagList});
+    html += '</p>';
+    return html;
+  };
+
+})(jQuery, Drupal, _, document);
diff --git a/core/modules/filter/filter.filter_html.admin.js b/core/modules/filter/filter.filter_html.admin.js
index 7ddca90bba84..cd1235a3f121 100644
--- a/core/modules/filter/filter.filter_html.admin.js
+++ b/core/modules/filter/filter.filter_html.admin.js
@@ -1,32 +1,21 @@
 /**
- * @file
- * Attaches behavior for updating filter_html's settings automatically.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/filter/filter.filter_html.admin.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, _, document) {
 
   'use strict';
 
   if (Drupal.filterConfiguration) {
-
-    /**
-     * Implement a live setting parser to prevent text editors from automatically
-     * enabling buttons that are not allowed by this filter's configuration.
-     *
-     * @namespace
-     */
     Drupal.filterConfiguration.liveSettingParsers.filter_html = {
-
-      /**
-       * @return {Array}
-       *   An array of filter rules.
-       */
-      getRules: function () {
+      getRules: function getRules() {
         var currentValue = $('#edit-filters-filter-html-settings-allowed-html').val();
         var rules = Drupal.behaviors.filterFilterHtmlUpdating._parseSetting(currentValue);
 
-        // Build a FilterHTMLRule that reflects the hard-coded behavior that
-        // strips all "style" attribute and all "on*" attributes.
         var rule = new Drupal.FilterHTMLRule();
         rule.restrictedTags.tags = ['*'];
         rule.restrictedTags.forbidden.attributes = ['style', 'on*'];
@@ -37,116 +26,67 @@
     };
   }
 
-  /**
-   * Displays and updates what HTML tags are allowed to use in a filter.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @todo Remove everything but 'attach' and 'detach' and make a proper object.
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches behavior for updating allowed HTML tags.
-   */
   Drupal.behaviors.filterFilterHtmlUpdating = {
-
-    // The form item contains the "Allowed HTML tags" setting.
     $allowedHTMLFormItem: null,
 
-    // The description for the "Allowed HTML tags" field.
     $allowedHTMLDescription: null,
 
-    /**
-     * The parsed, user-entered tag list of $allowedHTMLFormItem
-     *
-     * @var {Object.<string, Drupal.FilterHTMLRule>}
-     */
     userTags: {},
 
-    // The auto-created tag list thus far added.
     autoTags: null,
 
-    // Track which new features have been added to the text editor.
     newFeatures: {},
 
-    attach: function (context, settings) {
+    attach: function attach(context, settings) {
       var that = this;
       $(context).find('[name="filters[filter_html][settings][allowed_html]"]').once('filter-filter_html-updating').each(function () {
         that.$allowedHTMLFormItem = $(this);
         that.$allowedHTMLDescription = that.$allowedHTMLFormItem.closest('.js-form-item').find('.description');
         that.userTags = that._parseSetting(this.value);
 
-        // Update the new allowed tags based on added text editor features.
-        $(document)
-          .on('drupalEditorFeatureAdded', function (e, feature) {
+        $(document).on('drupalEditorFeatureAdded', function (e, feature) {
+          that.newFeatures[feature.name] = feature.rules;
+          that._updateAllowedTags();
+        }).on('drupalEditorFeatureModified', function (e, feature) {
+          if (that.newFeatures.hasOwnProperty(feature.name)) {
             that.newFeatures[feature.name] = feature.rules;
             that._updateAllowedTags();
-          })
-          .on('drupalEditorFeatureModified', function (e, feature) {
-            if (that.newFeatures.hasOwnProperty(feature.name)) {
-              that.newFeatures[feature.name] = feature.rules;
-              that._updateAllowedTags();
-            }
-          })
-          .on('drupalEditorFeatureRemoved', function (e, feature) {
-            if (that.newFeatures.hasOwnProperty(feature.name)) {
-              delete that.newFeatures[feature.name];
-              that._updateAllowedTags();
-            }
-          });
+          }
+        }).on('drupalEditorFeatureRemoved', function (e, feature) {
+          if (that.newFeatures.hasOwnProperty(feature.name)) {
+            delete that.newFeatures[feature.name];
+            that._updateAllowedTags();
+          }
+        });
 
-        // When the allowed tags list is manually changed, update userTags.
         that.$allowedHTMLFormItem.on('change.updateUserTags', function () {
           that.userTags = _.difference(that._parseSetting(this.value), that.autoTags);
         });
       });
     },
 
-    /**
-     * Updates the "Allowed HTML tags" setting and shows an informative message.
-     */
-    _updateAllowedTags: function () {
-      // Update the list of auto-created tags.
+    _updateAllowedTags: function _updateAllowedTags() {
       this.autoTags = this._calculateAutoAllowedTags(this.userTags, this.newFeatures);
 
-      // Remove any previous auto-created tag message.
       this.$allowedHTMLDescription.find('.editor-update-message').remove();
 
-      // If any auto-created tags: insert message and update form item.
       if (!_.isEmpty(this.autoTags)) {
         this.$allowedHTMLDescription.append(Drupal.theme('filterFilterHTMLUpdateMessage', this.autoTags));
         var userTagsWithoutOverrides = _.omit(this.userTags, _.keys(this.autoTags));
         this.$allowedHTMLFormItem.val(this._generateSetting(userTagsWithoutOverrides) + ' ' + this._generateSetting(this.autoTags));
-      }
-      // Restore to original state.
-      else {
-        this.$allowedHTMLFormItem.val(this._generateSetting(this.userTags));
-      }
+      } else {
+          this.$allowedHTMLFormItem.val(this._generateSetting(this.userTags));
+        }
     },
 
-    /**
-     * Calculates which HTML tags the added text editor buttons need to work.
-     *
-     * The filter_html filter is only concerned with the required tags, not with
-     * any properties, nor with each feature's "allowed" tags.
-     *
-     * @param {Array} userAllowedTags
-     *   The list of user-defined allowed tags.
-     * @param {object} newFeatures
-     *   A list of {@link Drupal.EditorFeature} objects' rules, keyed by
-     *   their name.
-     *
-     * @return {Array}
-     *   A list of new allowed tags.
-     */
-    _calculateAutoAllowedTags: function (userAllowedTags, newFeatures) {
+    _calculateAutoAllowedTags: function _calculateAutoAllowedTags(userAllowedTags, newFeatures) {
       var featureName;
       var feature;
       var featureRule;
       var filterRule;
       var tag;
       var editorRequiredTags = {};
-      // Map the newly added Text Editor features to Drupal.FilterHtmlRule
-      // objects (to allow comparing userTags with autoTags).
+
       for (featureName in newFeatures) {
         if (newFeatures.hasOwnProperty(featureName)) {
           feature = newFeatures[featureName];
@@ -157,79 +97,47 @@
               if (!_.has(editorRequiredTags, tag)) {
                 filterRule = new Drupal.FilterHTMLRule();
                 filterRule.restrictedTags.tags = [tag];
-                // @todo Neither Drupal.FilterHtmlRule nor
-                //   Drupal.EditorFeatureHTMLRule allow for generic attribute
-                //   value restrictions, only for the "class" and "style"
-                //   attribute's values to be restricted. The filter_html filter
-                //   always disallows the "style" attribute, so we only need to
-                //   support "class" attribute value restrictions. Fix once
-                //   https://www.drupal.org/node/2567801 lands.
+
                 filterRule.restrictedTags.allowed.attributes = featureRule.required.attributes.slice(0);
                 filterRule.restrictedTags.allowed.classes = featureRule.required.classes.slice(0);
                 editorRequiredTags[tag] = filterRule;
-              }
-              // The tag is already allowed, add any additionally allowed
-              // attributes.
-              else {
-                filterRule = editorRequiredTags[tag];
-                filterRule.restrictedTags.allowed.attributes = _.union(filterRule.restrictedTags.allowed.attributes, featureRule.required.attributes);
-                filterRule.restrictedTags.allowed.classes = _.union(filterRule.restrictedTags.allowed.classes, featureRule.required.classes);
-              }
+              } else {
+                  filterRule = editorRequiredTags[tag];
+                  filterRule.restrictedTags.allowed.attributes = _.union(filterRule.restrictedTags.allowed.attributes, featureRule.required.attributes);
+                  filterRule.restrictedTags.allowed.classes = _.union(filterRule.restrictedTags.allowed.classes, featureRule.required.classes);
+                }
             }
           }
         }
       }
 
-      // Now compare userAllowedTags with editorRequiredTags, and build
-      // autoAllowedTags, which contains:
-      // - any tags in editorRequiredTags but not in userAllowedTags (i.e. tags
-      //   that are additionally going to be allowed)
-      // - any tags in editorRequiredTags that already exists in userAllowedTags
-      //   but does not allow all attributes or attribute values
       var autoAllowedTags = {};
       for (tag in editorRequiredTags) {
-        // If userAllowedTags does not contain a rule for this editor-required
-        // tag, then add it to the list of automatically allowed tags.
         if (!_.has(userAllowedTags, tag)) {
           autoAllowedTags[tag] = editorRequiredTags[tag];
-        }
-        // Otherwise, if userAllowedTags already allows this tag, then check if
-        // additional attributes and classes on this tag are required by the
-        // editor.
-        else {
-          var requiredAttributes = editorRequiredTags[tag].restrictedTags.allowed.attributes;
-          var allowedAttributes = userAllowedTags[tag].restrictedTags.allowed.attributes;
-          var needsAdditionalAttributes = requiredAttributes.length && _.difference(requiredAttributes, allowedAttributes).length;
-          var requiredClasses = editorRequiredTags[tag].restrictedTags.allowed.classes;
-          var allowedClasses = userAllowedTags[tag].restrictedTags.allowed.classes;
-          var needsAdditionalClasses = requiredClasses.length && _.difference(requiredClasses, allowedClasses).length;
-          if (needsAdditionalAttributes || needsAdditionalClasses) {
-            autoAllowedTags[tag] = userAllowedTags[tag].clone();
-          }
-          if (needsAdditionalAttributes) {
-            autoAllowedTags[tag].restrictedTags.allowed.attributes = _.union(allowedAttributes, requiredAttributes);
-          }
-          if (needsAdditionalClasses) {
-            autoAllowedTags[tag].restrictedTags.allowed.classes = _.union(allowedClasses, requiredClasses);
+        } else {
+            var requiredAttributes = editorRequiredTags[tag].restrictedTags.allowed.attributes;
+            var allowedAttributes = userAllowedTags[tag].restrictedTags.allowed.attributes;
+            var needsAdditionalAttributes = requiredAttributes.length && _.difference(requiredAttributes, allowedAttributes).length;
+            var requiredClasses = editorRequiredTags[tag].restrictedTags.allowed.classes;
+            var allowedClasses = userAllowedTags[tag].restrictedTags.allowed.classes;
+            var needsAdditionalClasses = requiredClasses.length && _.difference(requiredClasses, allowedClasses).length;
+            if (needsAdditionalAttributes || needsAdditionalClasses) {
+              autoAllowedTags[tag] = userAllowedTags[tag].clone();
+            }
+            if (needsAdditionalAttributes) {
+              autoAllowedTags[tag].restrictedTags.allowed.attributes = _.union(allowedAttributes, requiredAttributes);
+            }
+            if (needsAdditionalClasses) {
+              autoAllowedTags[tag].restrictedTags.allowed.classes = _.union(allowedClasses, requiredClasses);
+            }
           }
-        }
       }
 
       return autoAllowedTags;
     },
 
-    /**
-     * Parses the value of this.$allowedHTMLFormItem.
-     *
-     * @param {string} setting
-     *   The string representation of the setting. For example:
-     *     <p class="callout"> <br> <a href hreflang>
-     *
-     * @return {Object.<string, Drupal.FilterHTMLRule>}
-     *   The corresponding text filter HTML rule objects, one per tag, keyed by
-     *   tag name.
-     */
-    _parseSetting: function (setting) {
+    _parseSetting: function _parseSetting(setting) {
       var node;
       var tag;
       var rule;
@@ -239,30 +147,23 @@
       var sandbox = document.createElement('div');
       var rules = {};
       for (var t = 0; t < allowedTags.length; t++) {
-        // Let the browser do the parsing work for us.
         sandbox.innerHTML = allowedTags[t];
         node = sandbox.firstChild;
         tag = node.tagName.toLowerCase();
 
-        // Build the Drupal.FilterHtmlRule object.
         rule = new Drupal.FilterHTMLRule();
-        // We create one rule per allowed tag, so always one tag.
+
         rule.restrictedTags.tags = [tag];
-        // Add the attribute restrictions.
+
         attributes = node.attributes;
         for (var i = 0; i < attributes.length; i++) {
           attribute = attributes.item(i);
           var attributeName = attribute.nodeName;
-          // @todo Drupal.FilterHtmlRule does not allow for generic attribute
-          //   value restrictions, only for the "class" and "style" attribute's
-          //   values. The filter_html filter always disallows the "style"
-          //   attribute, so we only need to support "class" attribute value
-          //   restrictions. Fix once https://www.drupal.org/node/2567801 lands.
+
           if (attributeName === 'class') {
             var attributeValue = attribute.textContent;
             rule.restrictedTags.allowed.classes = attributeValue.split(' ');
-          }
-          else {
+          } else {
             rule.restrictedTags.allowed.attributes.push(attributeName);
           }
         }
@@ -272,16 +173,7 @@
       return rules;
     },
 
-    /**
-     * Generates the value of this.$allowedHTMLFormItem.
-     *
-     * @param {Object.<string, Drupal.FilterHTMLRule>} tags
-     *   The parsed representation of the setting.
-     *
-     * @return {Array}
-     *   The string representation of the setting. e.g. "<p> <br> <a>"
-     */
-    _generateSetting: function (tags) {
+    _generateSetting: function _generateSetting(tags) {
       return _.reduce(tags, function (setting, rule, tag) {
         if (setting.length) {
           setting += ' ';
@@ -291,11 +183,7 @@
         if (rule.restrictedTags.allowed.attributes.length) {
           setting += ' ' + rule.restrictedTags.allowed.attributes.join(' ');
         }
-        // @todo Drupal.FilterHtmlRule does not allow for generic attribute
-        //   value restrictions, only for the "class" and "style" attribute's
-        //   values. The filter_html filter always disallows the "style"
-        //   attribute, so we only need to support "class" attribute value
-        //   restrictions. Fix once https://www.drupal.org/node/2567801 lands.
+
         if (rule.restrictedTags.allowed.classes.length) {
           setting += ' class="' + rule.restrictedTags.allowed.classes.join(' ') + '"';
         }
@@ -307,22 +195,12 @@
 
   };
 
-  /**
-   * Theme function for the filter_html update message.
-   *
-   * @param {Array} tags
-   *   An array of the new tags that are to be allowed.
-   *
-   * @return {string}
-   *   The corresponding HTML.
-   */
   Drupal.theme.filterFilterHTMLUpdateMessage = function (tags) {
     var html = '';
     var tagList = Drupal.behaviors.filterFilterHtmlUpdating._generateSetting(tags);
     html += '<p class="editor-update-message">';
-    html += Drupal.t('Based on the text editor configuration, these tags have automatically been added: <strong>@tag-list</strong>.', {'@tag-list': tagList});
+    html += Drupal.t('Based on the text editor configuration, these tags have automatically been added: <strong>@tag-list</strong>.', { '@tag-list': tagList });
     html += '</p>';
     return html;
   };
-
-})(jQuery, Drupal, _, document);
+})(jQuery, Drupal, _, document);
\ No newline at end of file
diff --git a/core/modules/filter/filter.js b/core/modules/filter/filter.js
index b79104733edc..2598bde610e9 100644
--- a/core/modules/filter/filter.js
+++ b/core/modules/filter/filter.js
@@ -1,39 +1,25 @@
 /**
- * @file
- * Attaches behavior for the Filter module.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/filter/filter.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   * Displays the guidelines of the selected text format automatically.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches behavior for updating filter guidelines.
-   */
   Drupal.behaviors.filterGuidelines = {
-    attach: function (context) {
+    attach: function attach(context) {
 
       function updateFilterGuidelines(event) {
         var $this = $(event.target);
         var value = $this.val();
-        $this.closest('.filter-wrapper')
-          .find('.filter-guidelines-item').hide()
-          .filter('.filter-guidelines-' + value).show();
+        $this.closest('.filter-wrapper').find('.filter-guidelines-item').hide().filter('.filter-guidelines-' + value).show();
       }
 
-      $(context).find('.filter-guidelines').once('filter-guidelines')
-        .find(':header').hide()
-        .closest('.filter-wrapper').find('select.filter-list')
-        .on('change.filterGuidelines', updateFilterGuidelines)
-        // Need to trigger the namespaced event to avoid triggering formUpdated
-        // when initializing the select.
-        .trigger('change.filterGuidelines');
+      $(context).find('.filter-guidelines').once('filter-guidelines').find(':header').hide().closest('.filter-wrapper').find('select.filter-list').on('change.filterGuidelines', updateFilterGuidelines).trigger('change.filterGuidelines');
     }
   };
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/modules/history/js/history.es6.js b/core/modules/history/js/history.es6.js
new file mode 100644
index 000000000000..6ce807c074d1
--- /dev/null
+++ b/core/modules/history/js/history.es6.js
@@ -0,0 +1,134 @@
+/**
+ * @file
+ * JavaScript API for the History module, with client-side caching.
+ *
+ * May only be loaded for authenticated users, with the History module enabled.
+ */
+
+(function ($, Drupal, drupalSettings, storage) {
+
+  'use strict';
+
+  var currentUserID = parseInt(drupalSettings.user.uid, 10);
+
+  // Any comment that is older than 30 days is automatically considered read,
+  // so for these we don't need to perform a request at all!
+  var thirtyDaysAgo = Math.round(new Date().getTime() / 1000) - 30 * 24 * 60 * 60;
+
+  // Use the data embedded in the page, if available.
+  var embeddedLastReadTimestamps = false;
+  if (drupalSettings.history && drupalSettings.history.lastReadTimestamps) {
+    embeddedLastReadTimestamps = drupalSettings.history.lastReadTimestamps;
+  }
+
+  /**
+   * @namespace
+   */
+  Drupal.history = {
+
+    /**
+     * Fetch "last read" timestamps for the given nodes.
+     *
+     * @param {Array} nodeIDs
+     *   An array of node IDs.
+     * @param {function} callback
+     *   A callback that is called after the requested timestamps were fetched.
+     */
+    fetchTimestamps: function (nodeIDs, callback) {
+      // Use the data embedded in the page, if available.
+      if (embeddedLastReadTimestamps) {
+        callback();
+        return;
+      }
+
+      $.ajax({
+        url: Drupal.url('history/get_node_read_timestamps'),
+        type: 'POST',
+        data: {'node_ids[]': nodeIDs},
+        dataType: 'json',
+        success: function (results) {
+          for (var nodeID in results) {
+            if (results.hasOwnProperty(nodeID)) {
+              storage.setItem('Drupal.history.' + currentUserID + '.' + nodeID, results[nodeID]);
+            }
+          }
+          callback();
+        }
+      });
+    },
+
+    /**
+     * Get the last read timestamp for the given node.
+     *
+     * @param {number|string} nodeID
+     *   A node ID.
+     *
+     * @return {number}
+     *   A UNIX timestamp.
+     */
+    getLastRead: function (nodeID) {
+      // Use the data embedded in the page, if available.
+      if (embeddedLastReadTimestamps && embeddedLastReadTimestamps[nodeID]) {
+        return parseInt(embeddedLastReadTimestamps[nodeID], 10);
+      }
+      return parseInt(storage.getItem('Drupal.history.' + currentUserID + '.' + nodeID) || 0, 10);
+    },
+
+    /**
+     * Marks a node as read, store the last read timestamp client-side.
+     *
+     * @param {number|string} nodeID
+     *   A node ID.
+     */
+    markAsRead: function (nodeID) {
+      $.ajax({
+        url: Drupal.url('history/' + nodeID + '/read'),
+        type: 'POST',
+        dataType: 'json',
+        success: function (timestamp) {
+          // If the data is embedded in the page, don't store on the client
+          // side.
+          if (embeddedLastReadTimestamps && embeddedLastReadTimestamps[nodeID]) {
+            return;
+          }
+
+          storage.setItem('Drupal.history.' + currentUserID + '.' + nodeID, timestamp);
+        }
+      });
+    },
+
+    /**
+     * Determines whether a server check is necessary.
+     *
+     * Any content that is >30 days old never gets a "new" or "updated"
+     * indicator. Any content that was published before the oldest known reading
+     * also never gets a "new" or "updated" indicator, because it must've been
+     * read already.
+     *
+     * @param {number|string} nodeID
+     *   A node ID.
+     * @param {number} contentTimestamp
+     *   The time at which some content (e.g. a comment) was published.
+     *
+     * @return {bool}
+     *   Whether a server check is necessary for the given node and its
+     *   timestamp.
+     */
+    needsServerCheck: function (nodeID, contentTimestamp) {
+      // First check if the content is older than 30 days, then we can bail
+      // early.
+      if (contentTimestamp < thirtyDaysAgo) {
+        return false;
+      }
+
+      // Use the data embedded in the page, if available.
+      if (embeddedLastReadTimestamps && embeddedLastReadTimestamps[nodeID]) {
+        return contentTimestamp > parseInt(embeddedLastReadTimestamps[nodeID], 10);
+      }
+
+      var minLastReadTimestamp = parseInt(storage.getItem('Drupal.history.' + currentUserID + '.' + nodeID) || 0, 10);
+      return contentTimestamp > minLastReadTimestamp;
+    }
+  };
+
+})(jQuery, Drupal, drupalSettings, window.localStorage);
diff --git a/core/modules/history/js/history.js b/core/modules/history/js/history.js
index 6ce807c074d1..c24f4e839ae7 100644
--- a/core/modules/history/js/history.js
+++ b/core/modules/history/js/history.js
@@ -1,9 +1,10 @@
 /**
- * @file
- * JavaScript API for the History module, with client-side caching.
- *
- * May only be loaded for authenticated users, with the History module enabled.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/history/js/history.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings, storage) {
 
@@ -11,31 +12,15 @@
 
   var currentUserID = parseInt(drupalSettings.user.uid, 10);
 
-  // Any comment that is older than 30 days is automatically considered read,
-  // so for these we don't need to perform a request at all!
   var thirtyDaysAgo = Math.round(new Date().getTime() / 1000) - 30 * 24 * 60 * 60;
 
-  // Use the data embedded in the page, if available.
   var embeddedLastReadTimestamps = false;
   if (drupalSettings.history && drupalSettings.history.lastReadTimestamps) {
     embeddedLastReadTimestamps = drupalSettings.history.lastReadTimestamps;
   }
 
-  /**
-   * @namespace
-   */
   Drupal.history = {
-
-    /**
-     * Fetch "last read" timestamps for the given nodes.
-     *
-     * @param {Array} nodeIDs
-     *   An array of node IDs.
-     * @param {function} callback
-     *   A callback that is called after the requested timestamps were fetched.
-     */
-    fetchTimestamps: function (nodeIDs, callback) {
-      // Use the data embedded in the page, if available.
+    fetchTimestamps: function fetchTimestamps(nodeIDs, callback) {
       if (embeddedLastReadTimestamps) {
         callback();
         return;
@@ -44,9 +29,9 @@
       $.ajax({
         url: Drupal.url('history/get_node_read_timestamps'),
         type: 'POST',
-        data: {'node_ids[]': nodeIDs},
+        data: { 'node_ids[]': nodeIDs },
         dataType: 'json',
-        success: function (results) {
+        success: function success(results) {
           for (var nodeID in results) {
             if (results.hasOwnProperty(nodeID)) {
               storage.setItem('Drupal.history.' + currentUserID + '.' + nodeID, results[nodeID]);
@@ -57,37 +42,19 @@
       });
     },
 
-    /**
-     * Get the last read timestamp for the given node.
-     *
-     * @param {number|string} nodeID
-     *   A node ID.
-     *
-     * @return {number}
-     *   A UNIX timestamp.
-     */
-    getLastRead: function (nodeID) {
-      // Use the data embedded in the page, if available.
+    getLastRead: function getLastRead(nodeID) {
       if (embeddedLastReadTimestamps && embeddedLastReadTimestamps[nodeID]) {
         return parseInt(embeddedLastReadTimestamps[nodeID], 10);
       }
       return parseInt(storage.getItem('Drupal.history.' + currentUserID + '.' + nodeID) || 0, 10);
     },
 
-    /**
-     * Marks a node as read, store the last read timestamp client-side.
-     *
-     * @param {number|string} nodeID
-     *   A node ID.
-     */
-    markAsRead: function (nodeID) {
+    markAsRead: function markAsRead(nodeID) {
       $.ajax({
         url: Drupal.url('history/' + nodeID + '/read'),
         type: 'POST',
         dataType: 'json',
-        success: function (timestamp) {
-          // If the data is embedded in the page, don't store on the client
-          // side.
+        success: function success(timestamp) {
           if (embeddedLastReadTimestamps && embeddedLastReadTimestamps[nodeID]) {
             return;
           }
@@ -97,31 +64,11 @@
       });
     },
 
-    /**
-     * Determines whether a server check is necessary.
-     *
-     * Any content that is >30 days old never gets a "new" or "updated"
-     * indicator. Any content that was published before the oldest known reading
-     * also never gets a "new" or "updated" indicator, because it must've been
-     * read already.
-     *
-     * @param {number|string} nodeID
-     *   A node ID.
-     * @param {number} contentTimestamp
-     *   The time at which some content (e.g. a comment) was published.
-     *
-     * @return {bool}
-     *   Whether a server check is necessary for the given node and its
-     *   timestamp.
-     */
-    needsServerCheck: function (nodeID, contentTimestamp) {
-      // First check if the content is older than 30 days, then we can bail
-      // early.
+    needsServerCheck: function needsServerCheck(nodeID, contentTimestamp) {
       if (contentTimestamp < thirtyDaysAgo) {
         return false;
       }
 
-      // Use the data embedded in the page, if available.
       if (embeddedLastReadTimestamps && embeddedLastReadTimestamps[nodeID]) {
         return contentTimestamp > parseInt(embeddedLastReadTimestamps[nodeID], 10);
       }
@@ -130,5 +77,4 @@
       return contentTimestamp > minLastReadTimestamp;
     }
   };
-
-})(jQuery, Drupal, drupalSettings, window.localStorage);
+})(jQuery, Drupal, drupalSettings, window.localStorage);
\ No newline at end of file
diff --git a/core/modules/history/js/mark-as-read.es6.js b/core/modules/history/js/mark-as-read.es6.js
new file mode 100644
index 000000000000..e225401e33ba
--- /dev/null
+++ b/core/modules/history/js/mark-as-read.es6.js
@@ -0,0 +1,23 @@
+/**
+ * @file
+ * Marks the nodes listed in drupalSettings.history.nodesToMarkAsRead as read.
+ *
+ * Uses the History module JavaScript API.
+ *
+ * @see Drupal.history
+ */
+
+(function (window, Drupal, drupalSettings) {
+
+  'use strict';
+
+  // When the window's "load" event is triggered, mark all enumerated nodes as
+  // read. This still allows for Drupal behaviors (which are triggered on the
+  // "DOMContentReady" event) to add "new" and "updated" indicators.
+  window.addEventListener('load', function () {
+    if (drupalSettings.history && drupalSettings.history.nodesToMarkAsRead) {
+      Object.keys(drupalSettings.history.nodesToMarkAsRead).forEach(Drupal.history.markAsRead);
+    }
+  });
+
+})(window, Drupal, drupalSettings);
diff --git a/core/modules/history/js/mark-as-read.js b/core/modules/history/js/mark-as-read.js
index e225401e33ba..bd8f8f0fb982 100644
--- a/core/modules/history/js/mark-as-read.js
+++ b/core/modules/history/js/mark-as-read.js
@@ -1,23 +1,18 @@
 /**
- * @file
- * Marks the nodes listed in drupalSettings.history.nodesToMarkAsRead as read.
- *
- * Uses the History module JavaScript API.
- *
- * @see Drupal.history
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/history/js/mark-as-read.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function (window, Drupal, drupalSettings) {
 
   'use strict';
 
-  // When the window's "load" event is triggered, mark all enumerated nodes as
-  // read. This still allows for Drupal behaviors (which are triggered on the
-  // "DOMContentReady" event) to add "new" and "updated" indicators.
   window.addEventListener('load', function () {
     if (drupalSettings.history && drupalSettings.history.nodesToMarkAsRead) {
       Object.keys(drupalSettings.history.nodesToMarkAsRead).forEach(Drupal.history.markAsRead);
     }
   });
-
-})(window, Drupal, drupalSettings);
+})(window, Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/modules/image/js/editors/image.es6.js b/core/modules/image/js/editors/image.es6.js
new file mode 100644
index 000000000000..dea08df59ee8
--- /dev/null
+++ b/core/modules/image/js/editors/image.es6.js
@@ -0,0 +1,342 @@
+/**
+ * @file
+ * Drag+drop based in-place editor for images.
+ */
+
+(function ($, _, Drupal) {
+
+  'use strict';
+
+  Drupal.quickedit.editors.image = Drupal.quickedit.EditorView.extend(/** @lends Drupal.quickedit.editors.image# */{
+
+    /**
+     * @constructs
+     *
+     * @augments Drupal.quickedit.EditorView
+     *
+     * @param {object} options
+     *   Options for the image editor.
+     */
+    initialize: function (options) {
+      Drupal.quickedit.EditorView.prototype.initialize.call(this, options);
+      // Set our original value to our current HTML (for reverting).
+      this.model.set('originalValue', this.$el.html().trim());
+      // $.val() callback function for copying input from our custom form to
+      // the Quick Edit Field Form.
+      this.model.set('currentValue', function (index, value) {
+        var matches = $(this).attr('name').match(/(alt|title)]$/);
+        if (matches) {
+          var name = matches[1];
+          var $toolgroup = $('#' + options.fieldModel.toolbarView.getMainWysiwygToolgroupId());
+          var $input = $toolgroup.find('.quickedit-image-field-info input[name="' + name + '"]');
+          if ($input.length) {
+            return $input.val();
+          }
+        }
+      });
+    },
+
+    /**
+     * @inheritdoc
+     *
+     * @param {Drupal.quickedit.FieldModel} fieldModel
+     *   The field model that holds the state.
+     * @param {string} state
+     *   The state to change to.
+     * @param {object} options
+     *   State options, if needed by the state change.
+     */
+    stateChange: function (fieldModel, state, options) {
+      var from = fieldModel.previous('state');
+      switch (state) {
+        case 'inactive':
+          break;
+
+        case 'candidate':
+          if (from !== 'inactive') {
+            this.$el.find('.quickedit-image-dropzone').remove();
+            this.$el.removeClass('quickedit-image-element');
+          }
+          if (from === 'invalid') {
+            this.removeValidationErrors();
+          }
+          break;
+
+        case 'highlighted':
+          break;
+
+        case 'activating':
+          // Defer updating the field model until the current state change has
+          // propagated, to not trigger a nested state change event.
+          _.defer(function () {
+            fieldModel.set('state', 'active');
+          });
+          break;
+
+        case 'active':
+          var self = this;
+
+          // Indicate that this element is being edited by Quick Edit Image.
+          this.$el.addClass('quickedit-image-element');
+
+          // Render our initial dropzone element. Once the user reverts changes
+          // or saves a new image, this element is removed.
+          var $dropzone = this.renderDropzone('upload', Drupal.t('Drop file here or click to upload'));
+
+          $dropzone.on('dragenter', function (e) {
+            $(this).addClass('hover');
+          });
+          $dropzone.on('dragleave', function (e) {
+            $(this).removeClass('hover');
+          });
+
+          $dropzone.on('drop', function (e) {
+            // Only respond when a file is dropped (could be another element).
+            if (e.originalEvent.dataTransfer && e.originalEvent.dataTransfer.files.length) {
+              $(this).removeClass('hover');
+              self.uploadImage(e.originalEvent.dataTransfer.files[0]);
+            }
+          });
+
+          $dropzone.on('click', function (e) {
+            // Create an <input> element without appending it to the DOM, and
+            // trigger a click event. This is the easiest way to arbitrarily
+            // open the browser's upload dialog.
+            $('<input type="file">')
+              .trigger('click')
+              .on('change', function () {
+                if (this.files.length) {
+                  self.uploadImage(this.files[0]);
+                }
+              });
+          });
+
+          // Prevent the browser's default behavior when dragging files onto
+          // the document (usually opens them in the same tab).
+          $dropzone.on('dragover dragenter dragleave drop click', function (e) {
+            e.preventDefault();
+            e.stopPropagation();
+          });
+
+          this.renderToolbar(fieldModel);
+          break;
+
+        case 'changed':
+          break;
+
+        case 'saving':
+          if (from === 'invalid') {
+            this.removeValidationErrors();
+          }
+
+          this.save(options);
+          break;
+
+        case 'saved':
+          break;
+
+        case 'invalid':
+          this.showValidationErrors();
+          break;
+      }
+    },
+
+    /**
+     * Validates/uploads a given file.
+     *
+     * @param {File} file
+     *   The file to upload.
+     */
+    uploadImage: function (file) {
+      // Indicate loading by adding a special class to our icon.
+      this.renderDropzone('upload loading', Drupal.t('Uploading <i>@file</i>…', {'@file': file.name}));
+
+      // Build a valid URL for our endpoint.
+      var fieldID = this.fieldModel.get('fieldID');
+      var url = Drupal.quickedit.util.buildUrl(fieldID, Drupal.url('quickedit/image/upload/!entity_type/!id/!field_name/!langcode/!view_mode'));
+
+      // Construct form data that our endpoint can consume.
+      var data = new FormData();
+      data.append('files[image]', file);
+
+      // Construct a POST request to our endpoint.
+      var self = this;
+      this.ajax({
+        type: 'POST',
+        url: url,
+        data: data,
+        success: function (response) {
+          var $el = $(self.fieldModel.get('el'));
+          // Indicate that the field has changed - this enables the
+          // "Save" button.
+          self.fieldModel.set('state', 'changed');
+          self.fieldModel.get('entity').set('inTempStore', true);
+          self.removeValidationErrors();
+
+          // Replace our html with the new image. If we replaced our entire
+          // element with data.html, we would have to implement complicated logic
+          // like what's in Drupal.quickedit.AppView.renderUpdatedField.
+          var $content = $(response.html).closest('[data-quickedit-field-id]').children();
+          $el.empty().append($content);
+        }
+      });
+    },
+
+    /**
+     * Utility function to make an AJAX request to the server.
+     *
+     * In addition to formatting the correct request, this also handles error
+     * codes and messages by displaying them visually inline with the image.
+     *
+     * Drupal.ajax is not called here as the Form API is unused by this
+     * in-place editor, and our JSON requests/responses try to be
+     * editor-agnostic. Ideally similar logic and routes could be used by
+     * modules like CKEditor for drag+drop file uploads as well.
+     *
+     * @param {object} options
+     *   Ajax options.
+     * @param {string} options.type
+     *   The type of request (i.e. GET, POST, PUT, DELETE, etc.)
+     * @param {string} options.url
+     *   The URL for the request.
+     * @param {*} options.data
+     *   The data to send to the server.
+     * @param {function} options.success
+     *   A callback function used when a request is successful, without errors.
+     */
+    ajax: function (options) {
+      var defaultOptions = {
+        context: this,
+        dataType: 'json',
+        cache: false,
+        contentType: false,
+        processData: false,
+        error: function () {
+          this.renderDropzone('error', Drupal.t('A server error has occurred.'));
+        }
+      };
+
+      var ajaxOptions = $.extend(defaultOptions, options);
+      var successCallback = ajaxOptions.success;
+
+      // Handle the success callback.
+      ajaxOptions.success = function (response) {
+        if (response.main_error) {
+          this.renderDropzone('error', response.main_error);
+          if (response.errors.length) {
+            this.model.set('validationErrors', response.errors);
+          }
+          this.showValidationErrors();
+        }
+        else {
+          successCallback(response);
+        }
+      };
+
+      $.ajax(ajaxOptions);
+    },
+
+    /**
+     * Renders our toolbar form for editing metadata.
+     *
+     * @param {Drupal.quickedit.FieldModel} fieldModel
+     *   The current Field Model.
+     */
+    renderToolbar: function (fieldModel) {
+      var $toolgroup = $('#' + fieldModel.toolbarView.getMainWysiwygToolgroupId());
+      var $toolbar = $toolgroup.find('.quickedit-image-field-info');
+      if ($toolbar.length === 0) {
+        // Perform an AJAX request for extra image info (alt/title).
+        var fieldID = fieldModel.get('fieldID');
+        var url = Drupal.quickedit.util.buildUrl(fieldID, Drupal.url('quickedit/image/info/!entity_type/!id/!field_name/!langcode/!view_mode'));
+        var self = this;
+        self.ajax({
+          type: 'GET',
+          url: url,
+          success: function (response) {
+            $toolbar = $(Drupal.theme.quickeditImageToolbar(response));
+            $toolgroup.append($toolbar);
+            $toolbar.on('keyup paste', function () {
+              fieldModel.set('state', 'changed');
+            });
+            // Re-position the toolbar, which could have changed size.
+            fieldModel.get('entity').toolbarView.position();
+          }
+        });
+      }
+    },
+
+    /**
+     * Renders our dropzone element.
+     *
+     * @param {string} state
+     *   The current state of our editor. Only used for visual styling.
+     * @param {string} text
+     *   The text to display in the dropzone area.
+     *
+     * @return {jQuery}
+     *   The rendered dropzone.
+     */
+    renderDropzone: function (state, text) {
+      var $dropzone = this.$el.find('.quickedit-image-dropzone');
+      // If the element already exists, modify its contents.
+      if ($dropzone.length) {
+        $dropzone
+          .removeClass('upload error hover loading')
+          .addClass('.quickedit-image-dropzone ' + state)
+          .children('.quickedit-image-text')
+            .html(text);
+      }
+      else {
+        $dropzone = $(Drupal.theme('quickeditImageDropzone', {
+          state: state,
+          text: text
+        }));
+        this.$el.append($dropzone);
+      }
+
+      return $dropzone;
+    },
+
+    /**
+     * @inheritdoc
+     */
+    revert: function () {
+      this.$el.html(this.model.get('originalValue'));
+    },
+
+    /**
+     * @inheritdoc
+     */
+    getQuickEditUISettings: function () {
+      return {padding: false, unifiedToolbar: true, fullWidthToolbar: true, popup: false};
+    },
+
+    /**
+     * @inheritdoc
+     */
+    showValidationErrors: function () {
+      var errors = Drupal.theme('quickeditImageErrors', {
+        errors: this.model.get('validationErrors')
+      });
+      $('#' + this.fieldModel.toolbarView.getMainWysiwygToolgroupId())
+        .append(errors);
+      this.getEditedElement()
+        .addClass('quickedit-validation-error');
+      // Re-position the toolbar, which could have changed size.
+      this.fieldModel.get('entity').toolbarView.position();
+    },
+
+    /**
+     * @inheritdoc
+     */
+    removeValidationErrors: function () {
+      $('#' + this.fieldModel.toolbarView.getMainWysiwygToolgroupId())
+        .find('.quickedit-image-errors').remove();
+      this.getEditedElement()
+        .removeClass('quickedit-validation-error');
+    }
+
+  });
+
+})(jQuery, _, Drupal);
diff --git a/core/modules/image/js/editors/image.js b/core/modules/image/js/editors/image.js
index dea08df59ee8..635cbaf5469d 100644
--- a/core/modules/image/js/editors/image.js
+++ b/core/modules/image/js/editors/image.js
@@ -1,28 +1,21 @@
 /**
- * @file
- * Drag+drop based in-place editor for images.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/image/js/editors/image.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, _, Drupal) {
 
   'use strict';
 
-  Drupal.quickedit.editors.image = Drupal.quickedit.EditorView.extend(/** @lends Drupal.quickedit.editors.image# */{
-
-    /**
-     * @constructs
-     *
-     * @augments Drupal.quickedit.EditorView
-     *
-     * @param {object} options
-     *   Options for the image editor.
-     */
-    initialize: function (options) {
+  Drupal.quickedit.editors.image = Drupal.quickedit.EditorView.extend({
+    initialize: function initialize(options) {
       Drupal.quickedit.EditorView.prototype.initialize.call(this, options);
-      // Set our original value to our current HTML (for reverting).
+
       this.model.set('originalValue', this.$el.html().trim());
-      // $.val() callback function for copying input from our custom form to
-      // the Quick Edit Field Form.
+
       this.model.set('currentValue', function (index, value) {
         var matches = $(this).attr('name').match(/(alt|title)]$/);
         if (matches) {
@@ -36,17 +29,7 @@
       });
     },
 
-    /**
-     * @inheritdoc
-     *
-     * @param {Drupal.quickedit.FieldModel} fieldModel
-     *   The field model that holds the state.
-     * @param {string} state
-     *   The state to change to.
-     * @param {object} options
-     *   State options, if needed by the state change.
-     */
-    stateChange: function (fieldModel, state, options) {
+    stateChange: function stateChange(fieldModel, state, options) {
       var from = fieldModel.previous('state');
       switch (state) {
         case 'inactive':
@@ -66,8 +49,6 @@
           break;
 
         case 'activating':
-          // Defer updating the field model until the current state change has
-          // propagated, to not trigger a nested state change event.
           _.defer(function () {
             fieldModel.set('state', 'active');
           });
@@ -76,11 +57,8 @@
         case 'active':
           var self = this;
 
-          // Indicate that this element is being edited by Quick Edit Image.
           this.$el.addClass('quickedit-image-element');
 
-          // Render our initial dropzone element. Once the user reverts changes
-          // or saves a new image, this element is removed.
           var $dropzone = this.renderDropzone('upload', Drupal.t('Drop file here or click to upload'));
 
           $dropzone.on('dragenter', function (e) {
@@ -91,7 +69,6 @@
           });
 
           $dropzone.on('drop', function (e) {
-            // Only respond when a file is dropped (could be another element).
             if (e.originalEvent.dataTransfer && e.originalEvent.dataTransfer.files.length) {
               $(this).removeClass('hover');
               self.uploadImage(e.originalEvent.dataTransfer.files[0]);
@@ -99,20 +76,13 @@
           });
 
           $dropzone.on('click', function (e) {
-            // Create an <input> element without appending it to the DOM, and
-            // trigger a click event. This is the easiest way to arbitrarily
-            // open the browser's upload dialog.
-            $('<input type="file">')
-              .trigger('click')
-              .on('change', function () {
-                if (this.files.length) {
-                  self.uploadImage(this.files[0]);
-                }
-              });
+            $('<input type="file">').trigger('click').on('change', function () {
+              if (this.files.length) {
+                self.uploadImage(this.files[0]);
+              }
+            });
           });
 
-          // Prevent the browser's default behavior when dragging files onto
-          // the document (usually opens them in the same tab).
           $dropzone.on('dragover dragenter dragleave drop click', function (e) {
             e.preventDefault();
             e.stopPropagation();
@@ -141,77 +111,41 @@
       }
     },
 
-    /**
-     * Validates/uploads a given file.
-     *
-     * @param {File} file
-     *   The file to upload.
-     */
-    uploadImage: function (file) {
-      // Indicate loading by adding a special class to our icon.
-      this.renderDropzone('upload loading', Drupal.t('Uploading <i>@file</i>…', {'@file': file.name}));
-
-      // Build a valid URL for our endpoint.
+    uploadImage: function uploadImage(file) {
+      this.renderDropzone('upload loading', Drupal.t('Uploading <i>@file</i>…', { '@file': file.name }));
+
       var fieldID = this.fieldModel.get('fieldID');
       var url = Drupal.quickedit.util.buildUrl(fieldID, Drupal.url('quickedit/image/upload/!entity_type/!id/!field_name/!langcode/!view_mode'));
 
-      // Construct form data that our endpoint can consume.
       var data = new FormData();
       data.append('files[image]', file);
 
-      // Construct a POST request to our endpoint.
       var self = this;
       this.ajax({
         type: 'POST',
         url: url,
         data: data,
-        success: function (response) {
+        success: function success(response) {
           var $el = $(self.fieldModel.get('el'));
-          // Indicate that the field has changed - this enables the
-          // "Save" button.
+
           self.fieldModel.set('state', 'changed');
           self.fieldModel.get('entity').set('inTempStore', true);
           self.removeValidationErrors();
 
-          // Replace our html with the new image. If we replaced our entire
-          // element with data.html, we would have to implement complicated logic
-          // like what's in Drupal.quickedit.AppView.renderUpdatedField.
           var $content = $(response.html).closest('[data-quickedit-field-id]').children();
           $el.empty().append($content);
         }
       });
     },
 
-    /**
-     * Utility function to make an AJAX request to the server.
-     *
-     * In addition to formatting the correct request, this also handles error
-     * codes and messages by displaying them visually inline with the image.
-     *
-     * Drupal.ajax is not called here as the Form API is unused by this
-     * in-place editor, and our JSON requests/responses try to be
-     * editor-agnostic. Ideally similar logic and routes could be used by
-     * modules like CKEditor for drag+drop file uploads as well.
-     *
-     * @param {object} options
-     *   Ajax options.
-     * @param {string} options.type
-     *   The type of request (i.e. GET, POST, PUT, DELETE, etc.)
-     * @param {string} options.url
-     *   The URL for the request.
-     * @param {*} options.data
-     *   The data to send to the server.
-     * @param {function} options.success
-     *   A callback function used when a request is successful, without errors.
-     */
-    ajax: function (options) {
+    ajax: function ajax(options) {
       var defaultOptions = {
         context: this,
         dataType: 'json',
         cache: false,
         contentType: false,
         processData: false,
-        error: function () {
+        error: function error() {
           this.renderDropzone('error', Drupal.t('A server error has occurred.'));
         }
       };
@@ -219,7 +153,6 @@
       var ajaxOptions = $.extend(defaultOptions, options);
       var successCallback = ajaxOptions.success;
 
-      // Handle the success callback.
       ajaxOptions.success = function (response) {
         if (response.main_error) {
           this.renderDropzone('error', response.main_error);
@@ -227,8 +160,7 @@
             this.model.set('validationErrors', response.errors);
           }
           this.showValidationErrors();
-        }
-        else {
+        } else {
           successCallback(response);
         }
       };
@@ -236,58 +168,35 @@
       $.ajax(ajaxOptions);
     },
 
-    /**
-     * Renders our toolbar form for editing metadata.
-     *
-     * @param {Drupal.quickedit.FieldModel} fieldModel
-     *   The current Field Model.
-     */
-    renderToolbar: function (fieldModel) {
+    renderToolbar: function renderToolbar(fieldModel) {
       var $toolgroup = $('#' + fieldModel.toolbarView.getMainWysiwygToolgroupId());
       var $toolbar = $toolgroup.find('.quickedit-image-field-info');
       if ($toolbar.length === 0) {
-        // Perform an AJAX request for extra image info (alt/title).
         var fieldID = fieldModel.get('fieldID');
         var url = Drupal.quickedit.util.buildUrl(fieldID, Drupal.url('quickedit/image/info/!entity_type/!id/!field_name/!langcode/!view_mode'));
         var self = this;
         self.ajax({
           type: 'GET',
           url: url,
-          success: function (response) {
+          success: function success(response) {
             $toolbar = $(Drupal.theme.quickeditImageToolbar(response));
             $toolgroup.append($toolbar);
             $toolbar.on('keyup paste', function () {
               fieldModel.set('state', 'changed');
             });
-            // Re-position the toolbar, which could have changed size.
+
             fieldModel.get('entity').toolbarView.position();
           }
         });
       }
     },
 
-    /**
-     * Renders our dropzone element.
-     *
-     * @param {string} state
-     *   The current state of our editor. Only used for visual styling.
-     * @param {string} text
-     *   The text to display in the dropzone area.
-     *
-     * @return {jQuery}
-     *   The rendered dropzone.
-     */
-    renderDropzone: function (state, text) {
+    renderDropzone: function renderDropzone(state, text) {
       var $dropzone = this.$el.find('.quickedit-image-dropzone');
-      // If the element already exists, modify its contents.
+
       if ($dropzone.length) {
-        $dropzone
-          .removeClass('upload error hover loading')
-          .addClass('.quickedit-image-dropzone ' + state)
-          .children('.quickedit-image-text')
-            .html(text);
-      }
-      else {
+        $dropzone.removeClass('upload error hover loading').addClass('.quickedit-image-dropzone ' + state).children('.quickedit-image-text').html(text);
+      } else {
         $dropzone = $(Drupal.theme('quickeditImageDropzone', {
           state: state,
           text: text
@@ -298,45 +207,28 @@
       return $dropzone;
     },
 
-    /**
-     * @inheritdoc
-     */
-    revert: function () {
+    revert: function revert() {
       this.$el.html(this.model.get('originalValue'));
     },
 
-    /**
-     * @inheritdoc
-     */
-    getQuickEditUISettings: function () {
-      return {padding: false, unifiedToolbar: true, fullWidthToolbar: true, popup: false};
+    getQuickEditUISettings: function getQuickEditUISettings() {
+      return { padding: false, unifiedToolbar: true, fullWidthToolbar: true, popup: false };
     },
 
-    /**
-     * @inheritdoc
-     */
-    showValidationErrors: function () {
+    showValidationErrors: function showValidationErrors() {
       var errors = Drupal.theme('quickeditImageErrors', {
         errors: this.model.get('validationErrors')
       });
-      $('#' + this.fieldModel.toolbarView.getMainWysiwygToolgroupId())
-        .append(errors);
-      this.getEditedElement()
-        .addClass('quickedit-validation-error');
-      // Re-position the toolbar, which could have changed size.
+      $('#' + this.fieldModel.toolbarView.getMainWysiwygToolgroupId()).append(errors);
+      this.getEditedElement().addClass('quickedit-validation-error');
+
       this.fieldModel.get('entity').toolbarView.position();
     },
 
-    /**
-     * @inheritdoc
-     */
-    removeValidationErrors: function () {
-      $('#' + this.fieldModel.toolbarView.getMainWysiwygToolgroupId())
-        .find('.quickedit-image-errors').remove();
-      this.getEditedElement()
-        .removeClass('quickedit-validation-error');
+    removeValidationErrors: function removeValidationErrors() {
+      $('#' + this.fieldModel.toolbarView.getMainWysiwygToolgroupId()).find('.quickedit-image-errors').remove();
+      this.getEditedElement().removeClass('quickedit-validation-error');
     }
 
   });
-
-})(jQuery, _, Drupal);
+})(jQuery, _, Drupal);
\ No newline at end of file
diff --git a/core/modules/image/js/theme.es6.js b/core/modules/image/js/theme.es6.js
new file mode 100644
index 000000000000..cba8f7bbca6b
--- /dev/null
+++ b/core/modules/image/js/theme.es6.js
@@ -0,0 +1,86 @@
+/**
+ * @file
+ * Provides theme functions for image Quick Edit's client-side HTML.
+ */
+
+(function (Drupal) {
+
+  'use strict';
+
+  /**
+   * Theme function for validation errors of the Image in-place editor.
+   *
+   * @param {object} settings
+   *   Settings object used to construct the markup.
+   * @param {string} settings.errors
+   *   Already escaped HTML representing error messages.
+   *
+   * @return {string}
+   *   The corresponding HTML.
+   */
+  Drupal.theme.quickeditImageErrors = function (settings) {
+    return '<div class="quickedit-image-errors">' + settings.errors + '</div>';
+  };
+
+  /**
+   * Theme function for the dropzone element of the Image module's in-place
+   * editor.
+   *
+   * @param {object} settings
+   *   Settings object used to construct the markup.
+   * @param {string} settings.state
+   *   State of the upload.
+   * @param {string} settings.text
+   *   Text to display inline with the dropzone element.
+   *
+   * @return {string}
+   *   The corresponding HTML.
+   */
+  Drupal.theme.quickeditImageDropzone = function (settings) {
+    return '<div class="quickedit-image-dropzone ' + settings.state + '">' +
+      '  <i class="quickedit-image-icon"></i>' +
+      '  <span class="quickedit-image-text">' + settings.text + '</span>' +
+      '</div>';
+  };
+
+  /**
+   * Theme function for the toolbar of the Image module's in-place editor.
+   *
+   * @param {object} settings
+   *   Settings object used to construct the markup.
+   * @param {bool} settings.alt_field
+   *   Whether or not the "Alt" field is enabled for this field.
+   * @param {bool} settings.alt_field_required
+   *   Whether or not the "Alt" field is required for this field.
+   * @param {string} settings.alt
+   *   The current value for the "Alt" field.
+   * @param {bool} settings.title_field
+   *   Whether or not the "Title" field is enabled for this field.
+   * @param {bool} settings.title_field_required
+   *   Whether or not the "Title" field is required for this field.
+   * @param {string} settings.title
+   *   The current value for the "Title" field.
+   *
+   * @return {string}
+   *   The corresponding HTML.
+   */
+  Drupal.theme.quickeditImageToolbar = function (settings) {
+    var html = '<form class="quickedit-image-field-info">';
+    if (settings.alt_field) {
+      html += '  <div>' +
+        '    <label for="alt" class="' + (settings.alt_field_required ? 'required' : '') + '">' + Drupal.t('Alternative text') + '</label>' +
+        '    <input type="text" placeholder="' + settings.alt + '" value="' + settings.alt + '" name="alt" ' + (settings.alt_field_required ? 'required' : '') + '/>' +
+        '  </div>';
+    }
+    if (settings.title_field) {
+      html += '  <div>' +
+        '    <label for="title" class="' + (settings.title_field_required ? 'form-required' : '') + '">' + Drupal.t('Title') + '</label>' +
+        '    <input type="text" placeholder="' + settings.title + '" value="' + settings.title + '" name="title" ' + (settings.title_field_required ? 'required' : '') + '/>' +
+        '  </div>';
+    }
+    html += '</form>';
+
+    return html;
+  };
+
+})(Drupal);
diff --git a/core/modules/image/js/theme.js b/core/modules/image/js/theme.js
index cba8f7bbca6b..dadc863203d5 100644
--- a/core/modules/image/js/theme.js
+++ b/core/modules/image/js/theme.js
@@ -1,86 +1,33 @@
 /**
- * @file
- * Provides theme functions for image Quick Edit's client-side HTML.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/image/js/theme.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function (Drupal) {
 
   'use strict';
 
-  /**
-   * Theme function for validation errors of the Image in-place editor.
-   *
-   * @param {object} settings
-   *   Settings object used to construct the markup.
-   * @param {string} settings.errors
-   *   Already escaped HTML representing error messages.
-   *
-   * @return {string}
-   *   The corresponding HTML.
-   */
   Drupal.theme.quickeditImageErrors = function (settings) {
     return '<div class="quickedit-image-errors">' + settings.errors + '</div>';
   };
 
-  /**
-   * Theme function for the dropzone element of the Image module's in-place
-   * editor.
-   *
-   * @param {object} settings
-   *   Settings object used to construct the markup.
-   * @param {string} settings.state
-   *   State of the upload.
-   * @param {string} settings.text
-   *   Text to display inline with the dropzone element.
-   *
-   * @return {string}
-   *   The corresponding HTML.
-   */
   Drupal.theme.quickeditImageDropzone = function (settings) {
-    return '<div class="quickedit-image-dropzone ' + settings.state + '">' +
-      '  <i class="quickedit-image-icon"></i>' +
-      '  <span class="quickedit-image-text">' + settings.text + '</span>' +
-      '</div>';
+    return '<div class="quickedit-image-dropzone ' + settings.state + '">' + '  <i class="quickedit-image-icon"></i>' + '  <span class="quickedit-image-text">' + settings.text + '</span>' + '</div>';
   };
 
-  /**
-   * Theme function for the toolbar of the Image module's in-place editor.
-   *
-   * @param {object} settings
-   *   Settings object used to construct the markup.
-   * @param {bool} settings.alt_field
-   *   Whether or not the "Alt" field is enabled for this field.
-   * @param {bool} settings.alt_field_required
-   *   Whether or not the "Alt" field is required for this field.
-   * @param {string} settings.alt
-   *   The current value for the "Alt" field.
-   * @param {bool} settings.title_field
-   *   Whether or not the "Title" field is enabled for this field.
-   * @param {bool} settings.title_field_required
-   *   Whether or not the "Title" field is required for this field.
-   * @param {string} settings.title
-   *   The current value for the "Title" field.
-   *
-   * @return {string}
-   *   The corresponding HTML.
-   */
   Drupal.theme.quickeditImageToolbar = function (settings) {
     var html = '<form class="quickedit-image-field-info">';
     if (settings.alt_field) {
-      html += '  <div>' +
-        '    <label for="alt" class="' + (settings.alt_field_required ? 'required' : '') + '">' + Drupal.t('Alternative text') + '</label>' +
-        '    <input type="text" placeholder="' + settings.alt + '" value="' + settings.alt + '" name="alt" ' + (settings.alt_field_required ? 'required' : '') + '/>' +
-        '  </div>';
+      html += '  <div>' + '    <label for="alt" class="' + (settings.alt_field_required ? 'required' : '') + '">' + Drupal.t('Alternative text') + '</label>' + '    <input type="text" placeholder="' + settings.alt + '" value="' + settings.alt + '" name="alt" ' + (settings.alt_field_required ? 'required' : '') + '/>' + '  </div>';
     }
     if (settings.title_field) {
-      html += '  <div>' +
-        '    <label for="title" class="' + (settings.title_field_required ? 'form-required' : '') + '">' + Drupal.t('Title') + '</label>' +
-        '    <input type="text" placeholder="' + settings.title + '" value="' + settings.title + '" name="title" ' + (settings.title_field_required ? 'required' : '') + '/>' +
-        '  </div>';
+      html += '  <div>' + '    <label for="title" class="' + (settings.title_field_required ? 'form-required' : '') + '">' + Drupal.t('Title') + '</label>' + '    <input type="text" placeholder="' + settings.title + '" value="' + settings.title + '" name="title" ' + (settings.title_field_required ? 'required' : '') + '/>' + '  </div>';
     }
     html += '</form>';
 
     return html;
   };
-
-})(Drupal);
+})(Drupal);
\ No newline at end of file
diff --git a/core/modules/language/language.admin.es6.js b/core/modules/language/language.admin.es6.js
new file mode 100644
index 000000000000..9d3daa4ce25f
--- /dev/null
+++ b/core/modules/language/language.admin.es6.js
@@ -0,0 +1,43 @@
+/**
+ * @file
+ * Language admin behavior.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * Makes language negotiation inherit user interface negotiation.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attach behavior to language negotiation admin user interface.
+   */
+  Drupal.behaviors.negotiationLanguage = {
+    attach: function () {
+      var $configForm = $('#language-negotiation-configure-form');
+      var inputSelector = 'input[name$="[configurable]"]';
+      // Given a customization checkbox derive the language type being changed.
+      function toggleTable(checkbox) {
+        var $checkbox = $(checkbox);
+        // Get the language detection type such as Interface text language
+        // detection or Content language detection.
+        $checkbox.closest('.table-language-group')
+          .find('table, .tabledrag-toggle-weight')
+          .toggle($checkbox.prop('checked'));
+      }
+
+      // Bind hide/show and rearrange customization checkboxes.
+      $configForm.once('negotiation-language-admin-bind').on('change', inputSelector, function (event) {
+        toggleTable(event.target);
+      });
+      // Initially, hide language detection types that are not customized.
+      $configForm.find(inputSelector + ':not(:checked)').each(function (index, element) {
+        toggleTable(element);
+      });
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/core/modules/language/language.admin.js b/core/modules/language/language.admin.js
index 9d3daa4ce25f..b08a55dfe8b6 100644
--- a/core/modules/language/language.admin.js
+++ b/core/modules/language/language.admin.js
@@ -1,43 +1,33 @@
 /**
- * @file
- * Language admin behavior.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/language/language.admin.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   * Makes language negotiation inherit user interface negotiation.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attach behavior to language negotiation admin user interface.
-   */
   Drupal.behaviors.negotiationLanguage = {
-    attach: function () {
+    attach: function attach() {
       var $configForm = $('#language-negotiation-configure-form');
       var inputSelector = 'input[name$="[configurable]"]';
-      // Given a customization checkbox derive the language type being changed.
+
       function toggleTable(checkbox) {
         var $checkbox = $(checkbox);
-        // Get the language detection type such as Interface text language
-        // detection or Content language detection.
-        $checkbox.closest('.table-language-group')
-          .find('table, .tabledrag-toggle-weight')
-          .toggle($checkbox.prop('checked'));
+
+        $checkbox.closest('.table-language-group').find('table, .tabledrag-toggle-weight').toggle($checkbox.prop('checked'));
       }
 
-      // Bind hide/show and rearrange customization checkboxes.
       $configForm.once('negotiation-language-admin-bind').on('change', inputSelector, function (event) {
         toggleTable(event.target);
       });
-      // Initially, hide language detection types that are not customized.
+
       $configForm.find(inputSelector + ':not(:checked)').each(function (index, element) {
         toggleTable(element);
       });
     }
   };
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/modules/locale/locale.admin.es6.js b/core/modules/locale/locale.admin.es6.js
new file mode 100644
index 000000000000..d9701ef94da8
--- /dev/null
+++ b/core/modules/locale/locale.admin.es6.js
@@ -0,0 +1,116 @@
+/**
+ * @file
+ * Locale admin behavior.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * Marks changes of translations.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches behavior to show the user if translations has changed.
+   * @prop {Drupal~behaviorDetach} detach
+   *   Detach behavior to show the user if translations has changed.
+   */
+  Drupal.behaviors.localeTranslateDirty = {
+    attach: function () {
+      var $form = $('#locale-translate-edit-form').once('localetranslatedirty');
+      if ($form.length) {
+        // Display a notice if any row changed.
+        $form.one('formUpdated.localeTranslateDirty', 'table', function () {
+          var $marker = $(Drupal.theme('localeTranslateChangedWarning')).hide();
+          $(this).addClass('changed').before($marker);
+          $marker.fadeIn('slow');
+        });
+        // Highlight changed row.
+        $form.on('formUpdated.localeTranslateDirty', 'tr', function () {
+          var $row = $(this);
+          var $rowToMark = $row.once('localemark');
+          var marker = Drupal.theme('localeTranslateChangedMarker');
+
+          $row.addClass('changed');
+          // Add an asterisk only once if row changed.
+          if ($rowToMark.length) {
+            $rowToMark.find('td:first-child .js-form-item').append(marker);
+          }
+        });
+      }
+    },
+    detach: function (context, settings, trigger) {
+      if (trigger === 'unload') {
+        var $form = $('#locale-translate-edit-form').removeOnce('localetranslatedirty');
+        if ($form.length) {
+          $form.off('formUpdated.localeTranslateDirty');
+        }
+      }
+    }
+  };
+
+  /**
+   * Show/hide the description details on Available translation updates page.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches behavior for toggling details on the translation update page.
+   */
+  Drupal.behaviors.hideUpdateInformation = {
+    attach: function (context, settings) {
+      var $table = $('#locale-translation-status-form').once('expand-updates');
+      if ($table.length) {
+        var $tbodies = $table.find('tbody');
+
+        // Open/close the description details by toggling a tr class.
+        $tbodies.on('click keydown', '.description', function (e) {
+          if (e.keyCode && (e.keyCode !== 13 && e.keyCode !== 32)) {
+            return;
+          }
+          e.preventDefault();
+          var $tr = $(this).closest('tr');
+
+          $tr.toggleClass('expanded');
+
+          // Change screen reader text.
+          $tr.find('.locale-translation-update__prefix').text(function () {
+            if ($tr.hasClass('expanded')) {
+              return Drupal.t('Hide description');
+            }
+            else {
+              return Drupal.t('Show description');
+            }
+          });
+        });
+        $table.find('.requirements, .links').hide();
+      }
+    }
+  };
+
+  $.extend(Drupal.theme, /** @lends Drupal.theme */{
+
+    /**
+     * Creates markup for a changed translation marker.
+     *
+     * @return {string}
+     *   Markup for the marker.
+     */
+    localeTranslateChangedMarker: function () {
+      return '<abbr class="warning ajax-changed" title="' + Drupal.t('Changed') + '">*</abbr>';
+    },
+
+    /**
+     * Creates markup for the translation changed warning.
+     *
+     * @return {string}
+     *   Markup for the warning.
+     */
+    localeTranslateChangedWarning: function () {
+      return '<div class="clearfix messages messages--warning">' + Drupal.theme('localeTranslateChangedMarker') + ' ' + Drupal.t('Changes made in this table will not be saved until the form is submitted.') + '</div>';
+    }
+  });
+
+})(jQuery, Drupal);
diff --git a/core/modules/locale/locale.admin.js b/core/modules/locale/locale.admin.js
index d9701ef94da8..f287542d9b17 100644
--- a/core/modules/locale/locale.admin.js
+++ b/core/modules/locale/locale.admin.js
@@ -1,47 +1,39 @@
 /**
- * @file
- * Locale admin behavior.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/locale/locale.admin.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   * Marks changes of translations.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches behavior to show the user if translations has changed.
-   * @prop {Drupal~behaviorDetach} detach
-   *   Detach behavior to show the user if translations has changed.
-   */
   Drupal.behaviors.localeTranslateDirty = {
-    attach: function () {
+    attach: function attach() {
       var $form = $('#locale-translate-edit-form').once('localetranslatedirty');
       if ($form.length) {
-        // Display a notice if any row changed.
         $form.one('formUpdated.localeTranslateDirty', 'table', function () {
           var $marker = $(Drupal.theme('localeTranslateChangedWarning')).hide();
           $(this).addClass('changed').before($marker);
           $marker.fadeIn('slow');
         });
-        // Highlight changed row.
+
         $form.on('formUpdated.localeTranslateDirty', 'tr', function () {
           var $row = $(this);
           var $rowToMark = $row.once('localemark');
           var marker = Drupal.theme('localeTranslateChangedMarker');
 
           $row.addClass('changed');
-          // Add an asterisk only once if row changed.
+
           if ($rowToMark.length) {
             $rowToMark.find('td:first-child .js-form-item').append(marker);
           }
         });
       }
     },
-    detach: function (context, settings, trigger) {
+    detach: function detach(context, settings, trigger) {
       if (trigger === 'unload') {
         var $form = $('#locale-translate-edit-form').removeOnce('localetranslatedirty');
         if ($form.length) {
@@ -51,23 +43,14 @@
     }
   };
 
-  /**
-   * Show/hide the description details on Available translation updates page.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches behavior for toggling details on the translation update page.
-   */
   Drupal.behaviors.hideUpdateInformation = {
-    attach: function (context, settings) {
+    attach: function attach(context, settings) {
       var $table = $('#locale-translation-status-form').once('expand-updates');
       if ($table.length) {
         var $tbodies = $table.find('tbody');
 
-        // Open/close the description details by toggling a tr class.
         $tbodies.on('click keydown', '.description', function (e) {
-          if (e.keyCode && (e.keyCode !== 13 && e.keyCode !== 32)) {
+          if (e.keyCode && e.keyCode !== 13 && e.keyCode !== 32) {
             return;
           }
           e.preventDefault();
@@ -75,12 +58,10 @@
 
           $tr.toggleClass('expanded');
 
-          // Change screen reader text.
           $tr.find('.locale-translation-update__prefix').text(function () {
             if ($tr.hasClass('expanded')) {
               return Drupal.t('Hide description');
-            }
-            else {
+            } else {
               return Drupal.t('Show description');
             }
           });
@@ -90,27 +71,13 @@
     }
   };
 
-  $.extend(Drupal.theme, /** @lends Drupal.theme */{
-
-    /**
-     * Creates markup for a changed translation marker.
-     *
-     * @return {string}
-     *   Markup for the marker.
-     */
-    localeTranslateChangedMarker: function () {
+  $.extend(Drupal.theme, {
+    localeTranslateChangedMarker: function localeTranslateChangedMarker() {
       return '<abbr class="warning ajax-changed" title="' + Drupal.t('Changed') + '">*</abbr>';
     },
 
-    /**
-     * Creates markup for the translation changed warning.
-     *
-     * @return {string}
-     *   Markup for the warning.
-     */
-    localeTranslateChangedWarning: function () {
+    localeTranslateChangedWarning: function localeTranslateChangedWarning() {
       return '<div class="clearfix messages messages--warning">' + Drupal.theme('localeTranslateChangedMarker') + ' ' + Drupal.t('Changes made in this table will not be saved until the form is submitted.') + '</div>';
     }
   });
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/modules/locale/locale.bulk.es6.js b/core/modules/locale/locale.bulk.es6.js
new file mode 100644
index 000000000000..c6743e832110
--- /dev/null
+++ b/core/modules/locale/locale.bulk.es6.js
@@ -0,0 +1,38 @@
+/**
+ * @file
+ * Locale behavior.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * Select the language code of an imported file based on its filename.
+   *
+   * This only works if the file name ends with "LANGCODE.po".
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches behavior for preselecting language code based on filename.
+   */
+  Drupal.behaviors.importLanguageCodeSelector = {
+    attach: function (context, settings) {
+      var $form = $('#locale-translate-import-form').once('autodetect-lang');
+      if ($form.length) {
+        var $langcode = $form.find('.langcode-input');
+        $form.find('.file-import-input')
+          .on('change', function () {
+            // If the filename is fully the language code or the filename
+            // ends with a language code, pre-select that one.
+            var matches = $(this).val().match(/([^.][\.]*)([\w-]+)\.po$/);
+            if (matches && $langcode.find('option[value="' + matches[2] + '"]').length) {
+              $langcode.val(matches[2]);
+            }
+          });
+      }
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/core/modules/locale/locale.bulk.js b/core/modules/locale/locale.bulk.js
index c6743e832110..e2da97851883 100644
--- a/core/modules/locale/locale.bulk.js
+++ b/core/modules/locale/locale.bulk.js
@@ -1,38 +1,27 @@
 /**
- * @file
- * Locale behavior.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/locale/locale.bulk.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   * Select the language code of an imported file based on its filename.
-   *
-   * This only works if the file name ends with "LANGCODE.po".
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches behavior for preselecting language code based on filename.
-   */
   Drupal.behaviors.importLanguageCodeSelector = {
-    attach: function (context, settings) {
+    attach: function attach(context, settings) {
       var $form = $('#locale-translate-import-form').once('autodetect-lang');
       if ($form.length) {
         var $langcode = $form.find('.langcode-input');
-        $form.find('.file-import-input')
-          .on('change', function () {
-            // If the filename is fully the language code or the filename
-            // ends with a language code, pre-select that one.
-            var matches = $(this).val().match(/([^.][\.]*)([\w-]+)\.po$/);
-            if (matches && $langcode.find('option[value="' + matches[2] + '"]').length) {
-              $langcode.val(matches[2]);
-            }
-          });
+        $form.find('.file-import-input').on('change', function () {
+          var matches = $(this).val().match(/([^.][\.]*)([\w-]+)\.po$/);
+          if (matches && $langcode.find('option[value="' + matches[2] + '"]').length) {
+            $langcode.val(matches[2]);
+          }
+        });
       }
     }
   };
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/modules/locale/locale.datepicker.es6.js b/core/modules/locale/locale.datepicker.es6.js
new file mode 100644
index 000000000000..a952ade320e7
--- /dev/null
+++ b/core/modules/locale/locale.datepicker.es6.js
@@ -0,0 +1,88 @@
+/**
+ * @file
+ * Datepicker JavaScript for the Locale module.
+ */
+
+(function ($, Drupal, drupalSettings) {
+
+  'use strict';
+
+  /**
+   * Attaches language support to the jQuery UI datepicker component.
+   *
+   * @type {Drupal~behavior}
+   */
+  Drupal.behaviors.localeDatepicker = {
+    attach: function (context, settings) {
+      // This code accesses drupalSettings and localized strings via Drupal.t().
+      // So this code should run after these are initialized. By placing it in an
+      // attach behavior this is assured.
+      $.datepicker.regional['drupal-locale'] = $.extend({
+        closeText: Drupal.t('Done'),
+        prevText: Drupal.t('Prev'),
+        nextText: Drupal.t('Next'),
+        currentText: Drupal.t('Today'),
+        monthNames: [
+          Drupal.t('January', {}, {context: 'Long month name'}),
+          Drupal.t('February', {}, {context: 'Long month name'}),
+          Drupal.t('March', {}, {context: 'Long month name'}),
+          Drupal.t('April', {}, {context: 'Long month name'}),
+          Drupal.t('May', {}, {context: 'Long month name'}),
+          Drupal.t('June', {}, {context: 'Long month name'}),
+          Drupal.t('July', {}, {context: 'Long month name'}),
+          Drupal.t('August', {}, {context: 'Long month name'}),
+          Drupal.t('September', {}, {context: 'Long month name'}),
+          Drupal.t('October', {}, {context: 'Long month name'}),
+          Drupal.t('November', {}, {context: 'Long month name'}),
+          Drupal.t('December', {}, {context: 'Long month name'})
+        ],
+        monthNamesShort: [
+          Drupal.t('Jan'),
+          Drupal.t('Feb'),
+          Drupal.t('Mar'),
+          Drupal.t('Apr'),
+          Drupal.t('May'),
+          Drupal.t('Jun'),
+          Drupal.t('Jul'),
+          Drupal.t('Aug'),
+          Drupal.t('Sep'),
+          Drupal.t('Oct'),
+          Drupal.t('Nov'),
+          Drupal.t('Dec')
+        ],
+        dayNames: [
+          Drupal.t('Sunday'),
+          Drupal.t('Monday'),
+          Drupal.t('Tuesday'),
+          Drupal.t('Wednesday'),
+          Drupal.t('Thursday'),
+          Drupal.t('Friday'),
+          Drupal.t('Saturday')
+        ],
+        dayNamesShort: [
+          Drupal.t('Sun'),
+          Drupal.t('Mon'),
+          Drupal.t('Tue'),
+          Drupal.t('Wed'),
+          Drupal.t('Thu'),
+          Drupal.t('Fri'),
+          Drupal.t('Sat')
+        ],
+        dayNamesMin: [
+          Drupal.t('Su'),
+          Drupal.t('Mo'),
+          Drupal.t('Tu'),
+          Drupal.t('We'),
+          Drupal.t('Th'),
+          Drupal.t('Fr'),
+          Drupal.t('Sa')
+        ],
+        dateFormat: Drupal.t('mm/dd/yy'),
+        firstDay: 0,
+        isRTL: 0
+      }, drupalSettings.jquery.ui.datepicker);
+      $.datepicker.setDefaults($.datepicker.regional['drupal-locale']);
+    }
+  };
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/locale/locale.datepicker.js b/core/modules/locale/locale.datepicker.js
index a952ade320e7..424c924cd86c 100644
--- a/core/modules/locale/locale.datepicker.js
+++ b/core/modules/locale/locale.datepicker.js
@@ -1,82 +1,27 @@
 /**
- * @file
- * Datepicker JavaScript for the Locale module.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/locale/locale.datepicker.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings) {
 
   'use strict';
 
-  /**
-   * Attaches language support to the jQuery UI datepicker component.
-   *
-   * @type {Drupal~behavior}
-   */
   Drupal.behaviors.localeDatepicker = {
-    attach: function (context, settings) {
-      // This code accesses drupalSettings and localized strings via Drupal.t().
-      // So this code should run after these are initialized. By placing it in an
-      // attach behavior this is assured.
+    attach: function attach(context, settings) {
       $.datepicker.regional['drupal-locale'] = $.extend({
         closeText: Drupal.t('Done'),
         prevText: Drupal.t('Prev'),
         nextText: Drupal.t('Next'),
         currentText: Drupal.t('Today'),
-        monthNames: [
-          Drupal.t('January', {}, {context: 'Long month name'}),
-          Drupal.t('February', {}, {context: 'Long month name'}),
-          Drupal.t('March', {}, {context: 'Long month name'}),
-          Drupal.t('April', {}, {context: 'Long month name'}),
-          Drupal.t('May', {}, {context: 'Long month name'}),
-          Drupal.t('June', {}, {context: 'Long month name'}),
-          Drupal.t('July', {}, {context: 'Long month name'}),
-          Drupal.t('August', {}, {context: 'Long month name'}),
-          Drupal.t('September', {}, {context: 'Long month name'}),
-          Drupal.t('October', {}, {context: 'Long month name'}),
-          Drupal.t('November', {}, {context: 'Long month name'}),
-          Drupal.t('December', {}, {context: 'Long month name'})
-        ],
-        monthNamesShort: [
-          Drupal.t('Jan'),
-          Drupal.t('Feb'),
-          Drupal.t('Mar'),
-          Drupal.t('Apr'),
-          Drupal.t('May'),
-          Drupal.t('Jun'),
-          Drupal.t('Jul'),
-          Drupal.t('Aug'),
-          Drupal.t('Sep'),
-          Drupal.t('Oct'),
-          Drupal.t('Nov'),
-          Drupal.t('Dec')
-        ],
-        dayNames: [
-          Drupal.t('Sunday'),
-          Drupal.t('Monday'),
-          Drupal.t('Tuesday'),
-          Drupal.t('Wednesday'),
-          Drupal.t('Thursday'),
-          Drupal.t('Friday'),
-          Drupal.t('Saturday')
-        ],
-        dayNamesShort: [
-          Drupal.t('Sun'),
-          Drupal.t('Mon'),
-          Drupal.t('Tue'),
-          Drupal.t('Wed'),
-          Drupal.t('Thu'),
-          Drupal.t('Fri'),
-          Drupal.t('Sat')
-        ],
-        dayNamesMin: [
-          Drupal.t('Su'),
-          Drupal.t('Mo'),
-          Drupal.t('Tu'),
-          Drupal.t('We'),
-          Drupal.t('Th'),
-          Drupal.t('Fr'),
-          Drupal.t('Sa')
-        ],
+        monthNames: [Drupal.t('January', {}, { context: 'Long month name' }), Drupal.t('February', {}, { context: 'Long month name' }), Drupal.t('March', {}, { context: 'Long month name' }), Drupal.t('April', {}, { context: 'Long month name' }), Drupal.t('May', {}, { context: 'Long month name' }), Drupal.t('June', {}, { context: 'Long month name' }), Drupal.t('July', {}, { context: 'Long month name' }), Drupal.t('August', {}, { context: 'Long month name' }), Drupal.t('September', {}, { context: 'Long month name' }), Drupal.t('October', {}, { context: 'Long month name' }), Drupal.t('November', {}, { context: 'Long month name' }), Drupal.t('December', {}, { context: 'Long month name' })],
+        monthNamesShort: [Drupal.t('Jan'), Drupal.t('Feb'), Drupal.t('Mar'), Drupal.t('Apr'), Drupal.t('May'), Drupal.t('Jun'), Drupal.t('Jul'), Drupal.t('Aug'), Drupal.t('Sep'), Drupal.t('Oct'), Drupal.t('Nov'), Drupal.t('Dec')],
+        dayNames: [Drupal.t('Sunday'), Drupal.t('Monday'), Drupal.t('Tuesday'), Drupal.t('Wednesday'), Drupal.t('Thursday'), Drupal.t('Friday'), Drupal.t('Saturday')],
+        dayNamesShort: [Drupal.t('Sun'), Drupal.t('Mon'), Drupal.t('Tue'), Drupal.t('Wed'), Drupal.t('Thu'), Drupal.t('Fri'), Drupal.t('Sat')],
+        dayNamesMin: [Drupal.t('Su'), Drupal.t('Mo'), Drupal.t('Tu'), Drupal.t('We'), Drupal.t('Th'), Drupal.t('Fr'), Drupal.t('Sa')],
         dateFormat: Drupal.t('mm/dd/yy'),
         firstDay: 0,
         isRTL: 0
@@ -84,5 +29,4 @@
       $.datepicker.setDefaults($.datepicker.regional['drupal-locale']);
     }
   };
-
-})(jQuery, Drupal, drupalSettings);
+})(jQuery, Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/modules/locale/tests/locale_test.es6.js b/core/modules/locale/tests/locale_test.es6.js
new file mode 100644
index 000000000000..ba62b7504e1e
--- /dev/null
+++ b/core/modules/locale/tests/locale_test.es6.js
@@ -0,0 +1,52 @@
+/**
+ * @file
+ * JavaScript for locale_test.module.
+ *
+ * @ignore
+ */
+
+Drupal.t("Standard Call t");
+Drupal
+  .
+  t
+  (
+    "Whitespace Call t"
+  )
+;
+
+Drupal.t('Single Quote t');
+Drupal.t('Single Quote \'Escaped\' t');
+Drupal.t('Single Quote ' + 'Concat ' + 'strings ' + 't');
+
+Drupal.t("Double Quote t");
+Drupal.t("Double Quote \"Escaped\" t");
+Drupal.t("Double Quote " + "Concat " + "strings " + "t");
+
+Drupal.t("Context Unquoted t", {}, {context: "Context string unquoted"});
+Drupal.t("Context Single Quoted t", {}, {'context': "Context string single quoted"});
+Drupal.t("Context Double Quoted t", {}, {"context": "Context string double quoted"});
+
+Drupal.t("Context !key Args t", {'!key': 'value'}, {context: "Context string"});
+
+Drupal.formatPlural(1, "Standard Call plural", "Standard Call @count plural");
+Drupal
+  .
+  formatPlural
+  (
+    1,
+    "Whitespace Call plural",
+    "Whitespace Call @count plural"
+  )
+;
+
+Drupal.formatPlural(1, 'Single Quote plural', 'Single Quote @count plural');
+Drupal.formatPlural(1, 'Single Quote \'Escaped\' plural', 'Single Quote \'Escaped\' @count plural');
+
+Drupal.formatPlural(1, "Double Quote plural", "Double Quote @count plural");
+Drupal.formatPlural(1, "Double Quote \"Escaped\" plural", "Double Quote \"Escaped\" @count plural");
+
+Drupal.formatPlural(1, "Context Unquoted plural", "Context Unquoted @count plural", {}, {context: "Context string unquoted"});
+Drupal.formatPlural(1, "Context Single Quoted plural", "Context Single Quoted @count plural", {}, {'context': "Context string single quoted"});
+Drupal.formatPlural(1, "Context Double Quoted plural", "Context Double Quoted @count plural", {}, {"context": "Context string double quoted"});
+
+Drupal.formatPlural(1, "Context !key Args plural", "Context !key Args @count plural", {'!key': 'value'}, {context: "Context string"});
diff --git a/core/modules/locale/tests/locale_test.js b/core/modules/locale/tests/locale_test.js
index ba62b7504e1e..def15853e8cd 100644
--- a/core/modules/locale/tests/locale_test.js
+++ b/core/modules/locale/tests/locale_test.js
@@ -1,18 +1,13 @@
 /**
- * @file
- * JavaScript for locale_test.module.
- *
- * @ignore
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/locale/tests/locale_test.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 Drupal.t("Standard Call t");
-Drupal
-  .
-  t
-  (
-    "Whitespace Call t"
-  )
-;
+Drupal.t("Whitespace Call t");
 
 Drupal.t('Single Quote t');
 Drupal.t('Single Quote \'Escaped\' t');
@@ -22,22 +17,14 @@ Drupal.t("Double Quote t");
 Drupal.t("Double Quote \"Escaped\" t");
 Drupal.t("Double Quote " + "Concat " + "strings " + "t");
 
-Drupal.t("Context Unquoted t", {}, {context: "Context string unquoted"});
-Drupal.t("Context Single Quoted t", {}, {'context': "Context string single quoted"});
-Drupal.t("Context Double Quoted t", {}, {"context": "Context string double quoted"});
+Drupal.t("Context Unquoted t", {}, { context: "Context string unquoted" });
+Drupal.t("Context Single Quoted t", {}, { 'context': "Context string single quoted" });
+Drupal.t("Context Double Quoted t", {}, { "context": "Context string double quoted" });
 
-Drupal.t("Context !key Args t", {'!key': 'value'}, {context: "Context string"});
+Drupal.t("Context !key Args t", { '!key': 'value' }, { context: "Context string" });
 
 Drupal.formatPlural(1, "Standard Call plural", "Standard Call @count plural");
-Drupal
-  .
-  formatPlural
-  (
-    1,
-    "Whitespace Call plural",
-    "Whitespace Call @count plural"
-  )
-;
+Drupal.formatPlural(1, "Whitespace Call plural", "Whitespace Call @count plural");
 
 Drupal.formatPlural(1, 'Single Quote plural', 'Single Quote @count plural');
 Drupal.formatPlural(1, 'Single Quote \'Escaped\' plural', 'Single Quote \'Escaped\' @count plural');
@@ -45,8 +32,8 @@ Drupal.formatPlural(1, 'Single Quote \'Escaped\' plural', 'Single Quote \'Escape
 Drupal.formatPlural(1, "Double Quote plural", "Double Quote @count plural");
 Drupal.formatPlural(1, "Double Quote \"Escaped\" plural", "Double Quote \"Escaped\" @count plural");
 
-Drupal.formatPlural(1, "Context Unquoted plural", "Context Unquoted @count plural", {}, {context: "Context string unquoted"});
-Drupal.formatPlural(1, "Context Single Quoted plural", "Context Single Quoted @count plural", {}, {'context': "Context string single quoted"});
-Drupal.formatPlural(1, "Context Double Quoted plural", "Context Double Quoted @count plural", {}, {"context": "Context string double quoted"});
+Drupal.formatPlural(1, "Context Unquoted plural", "Context Unquoted @count plural", {}, { context: "Context string unquoted" });
+Drupal.formatPlural(1, "Context Single Quoted plural", "Context Single Quoted @count plural", {}, { 'context': "Context string single quoted" });
+Drupal.formatPlural(1, "Context Double Quoted plural", "Context Double Quoted @count plural", {}, { "context": "Context string double quoted" });
 
-Drupal.formatPlural(1, "Context !key Args plural", "Context !key Args @count plural", {'!key': 'value'}, {context: "Context string"});
+Drupal.formatPlural(1, "Context !key Args plural", "Context !key Args @count plural", { '!key': 'value' }, { context: "Context string" });
\ No newline at end of file
diff --git a/core/modules/media/js/media_form.es6.js b/core/modules/media/js/media_form.es6.js
new file mode 100644
index 000000000000..c93eac6644e8
--- /dev/null
+++ b/core/modules/media/js/media_form.es6.js
@@ -0,0 +1,40 @@
+/**
+ * @file
+ * Defines Javascript behaviors for the media form.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * Behaviors for summaries for tabs in the media edit form.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches summary behavior for tabs in the media edit form.
+   */
+  Drupal.behaviors.mediaFormSummaries = {
+    attach: function (context) {
+      var $context = $(context);
+
+      $context.find('.media-form-author').drupalSetSummary(function (context) {
+        var $authorContext = $(context);
+        var name = $authorContext.find('.field--name-uid input').val();
+        var date = $authorContext.find('.field--name-created input').val();
+
+        if (name && date) {
+          return Drupal.t('By @name on @date', {'@name': name, '@date': date});
+        }
+        else if (name) {
+          return Drupal.t('By @name', {'@name': name});
+        }
+        else if (date) {
+          return Drupal.t('Authored on @date', {'@date': date});
+        }
+      });
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/core/modules/media/js/media_form.js b/core/modules/media/js/media_form.js
index c93eac6644e8..9fe3ee230cff 100644
--- a/core/modules/media/js/media_form.js
+++ b/core/modules/media/js/media_form.js
@@ -1,22 +1,17 @@
 /**
- * @file
- * Defines Javascript behaviors for the media form.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/media/js/media_form.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   * Behaviors for summaries for tabs in the media edit form.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches summary behavior for tabs in the media edit form.
-   */
   Drupal.behaviors.mediaFormSummaries = {
-    attach: function (context) {
+    attach: function attach(context) {
       var $context = $(context);
 
       $context.find('.media-form-author').drupalSetSummary(function (context) {
@@ -25,16 +20,13 @@
         var date = $authorContext.find('.field--name-created input').val();
 
         if (name && date) {
-          return Drupal.t('By @name on @date', {'@name': name, '@date': date});
-        }
-        else if (name) {
-          return Drupal.t('By @name', {'@name': name});
-        }
-        else if (date) {
-          return Drupal.t('Authored on @date', {'@date': date});
+          return Drupal.t('By @name on @date', { '@name': name, '@date': date });
+        } else if (name) {
+          return Drupal.t('By @name', { '@name': name });
+        } else if (date) {
+          return Drupal.t('Authored on @date', { '@date': date });
         }
       });
     }
   };
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/modules/media/js/media_type_form.es6.js b/core/modules/media/js/media_type_form.es6.js
new file mode 100644
index 000000000000..0c1d906e0178
--- /dev/null
+++ b/core/modules/media/js/media_type_form.es6.js
@@ -0,0 +1,46 @@
+/**
+ * @file
+ * Defines JavaScript behaviors for the media type form.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * Behaviors for setting summaries on media type form.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches summary behaviors on media type edit forms.
+   */
+  Drupal.behaviors.mediaTypeFormSummaries = {
+    attach: function (context) {
+      var $context = $(context);
+      // Provide the vertical tab summaries.
+      $context.find('#edit-workflow').drupalSetSummary(function (context) {
+        var vals = [];
+        $(context).find('input[name^="options"]:checked').parent().each(function () {
+          vals.push(Drupal.checkPlain($(this).find('label').text()));
+        });
+        if (!$(context).find('#edit-options-status').is(':checked')) {
+          vals.unshift(Drupal.t('Not published'));
+        }
+        return vals.join(', ');
+      });
+      $(context).find('#edit-language').drupalSetSummary(function (context) {
+        var vals = [];
+
+        vals.push($(context).find('.js-form-item-language-configuration-langcode select option:selected').text());
+
+        $(context).find('input:checked').next('label').each(function () {
+          vals.push(Drupal.checkPlain($(this).text()));
+        });
+
+        return vals.join(', ');
+      });
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/core/modules/media/js/media_type_form.js b/core/modules/media/js/media_type_form.js
index 0c1d906e0178..d5c4a0306104 100644
--- a/core/modules/media/js/media_type_form.js
+++ b/core/modules/media/js/media_type_form.js
@@ -1,24 +1,19 @@
 /**
- * @file
- * Defines JavaScript behaviors for the media type form.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/media/js/media_type_form.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   * Behaviors for setting summaries on media type form.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches summary behaviors on media type edit forms.
-   */
   Drupal.behaviors.mediaTypeFormSummaries = {
-    attach: function (context) {
+    attach: function attach(context) {
       var $context = $(context);
-      // Provide the vertical tab summaries.
+
       $context.find('#edit-workflow').drupalSetSummary(function (context) {
         var vals = [];
         $(context).find('input[name^="options"]:checked').parent().each(function () {
@@ -42,5 +37,4 @@
       });
     }
   };
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/modules/menu_ui/menu_ui.admin.es6.js b/core/modules/menu_ui/menu_ui.admin.es6.js
new file mode 100644
index 000000000000..345138f17a47
--- /dev/null
+++ b/core/modules/menu_ui/menu_ui.admin.es6.js
@@ -0,0 +1,68 @@
+/**
+ * @file
+ * Menu UI admin behaviors.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   *
+   * @type {Drupal~behavior}
+   */
+  Drupal.behaviors.menuUiChangeParentItems = {
+    attach: function (context, settings) {
+      var $menu = $('#edit-menu').once('menu-parent');
+      if ($menu.length) {
+        // Update the list of available parent menu items to match the initial
+        // available menus.
+        Drupal.menuUiUpdateParentList();
+
+        // Update list of available parent menu items.
+        $menu.on('change', 'input', Drupal.menuUiUpdateParentList);
+      }
+    }
+  };
+
+  /**
+   * Function to set the options of the menu parent item dropdown.
+   */
+  Drupal.menuUiUpdateParentList = function () {
+    var $menu = $('#edit-menu');
+    var values = [];
+
+    $menu.find('input:checked').each(function () {
+      // Get the names of all checked menus.
+      values.push(Drupal.checkPlain($.trim($(this).val())));
+    });
+
+    $.ajax({
+      url: location.protocol + '//' + location.host + Drupal.url('admin/structure/menu/parents'),
+      type: 'POST',
+      data: {'menus[]': values},
+      dataType: 'json',
+      success: function (options) {
+        var $select = $('#edit-menu-parent');
+        // Save key of last selected element.
+        var selected = $select.val();
+        // Remove all existing options from dropdown.
+        $select.children().remove();
+        // Add new options to dropdown. Keep a count of options for testing later.
+        var totalOptions = 0;
+        for (var machineName in options) {
+          if (options.hasOwnProperty(machineName)) {
+            $select.append(
+              $('<option ' + (machineName === selected ? ' selected="selected"' : '') + '></option>').val(machineName).text(options[machineName])
+            );
+            totalOptions++;
+          }
+        }
+
+        // Hide the parent options if there are no options for it.
+        $select.closest('div').toggle(totalOptions > 0).attr('hidden', totalOptions === 0);
+      }
+    });
+  };
+
+})(jQuery, Drupal);
diff --git a/core/modules/menu_ui/menu_ui.admin.js b/core/modules/menu_ui/menu_ui.admin.js
index 345138f17a47..854d05114232 100644
--- a/core/modules/menu_ui/menu_ui.admin.js
+++ b/core/modules/menu_ui/menu_ui.admin.js
@@ -1,68 +1,56 @@
 /**
- * @file
- * Menu UI admin behaviors.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/menu_ui/menu_ui.admin.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   *
-   * @type {Drupal~behavior}
-   */
   Drupal.behaviors.menuUiChangeParentItems = {
-    attach: function (context, settings) {
+    attach: function attach(context, settings) {
       var $menu = $('#edit-menu').once('menu-parent');
       if ($menu.length) {
-        // Update the list of available parent menu items to match the initial
-        // available menus.
         Drupal.menuUiUpdateParentList();
 
-        // Update list of available parent menu items.
         $menu.on('change', 'input', Drupal.menuUiUpdateParentList);
       }
     }
   };
 
-  /**
-   * Function to set the options of the menu parent item dropdown.
-   */
   Drupal.menuUiUpdateParentList = function () {
     var $menu = $('#edit-menu');
     var values = [];
 
     $menu.find('input:checked').each(function () {
-      // Get the names of all checked menus.
       values.push(Drupal.checkPlain($.trim($(this).val())));
     });
 
     $.ajax({
       url: location.protocol + '//' + location.host + Drupal.url('admin/structure/menu/parents'),
       type: 'POST',
-      data: {'menus[]': values},
+      data: { 'menus[]': values },
       dataType: 'json',
-      success: function (options) {
+      success: function success(options) {
         var $select = $('#edit-menu-parent');
-        // Save key of last selected element.
+
         var selected = $select.val();
-        // Remove all existing options from dropdown.
+
         $select.children().remove();
-        // Add new options to dropdown. Keep a count of options for testing later.
+
         var totalOptions = 0;
         for (var machineName in options) {
           if (options.hasOwnProperty(machineName)) {
-            $select.append(
-              $('<option ' + (machineName === selected ? ' selected="selected"' : '') + '></option>').val(machineName).text(options[machineName])
-            );
+            $select.append($('<option ' + (machineName === selected ? ' selected="selected"' : '') + '></option>').val(machineName).text(options[machineName]));
             totalOptions++;
           }
         }
 
-        // Hide the parent options if there are no options for it.
         $select.closest('div').toggle(totalOptions > 0).attr('hidden', totalOptions === 0);
       }
     });
   };
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/modules/menu_ui/menu_ui.es6.js b/core/modules/menu_ui/menu_ui.es6.js
new file mode 100644
index 000000000000..5eb10e0781c8
--- /dev/null
+++ b/core/modules/menu_ui/menu_ui.es6.js
@@ -0,0 +1,91 @@
+/**
+ * @file
+ * Menu UI behaviors.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * Set a summary on the menu link form.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Find the form and call `drupalSetSummary` on it.
+   */
+  Drupal.behaviors.menuUiDetailsSummaries = {
+    attach: function (context) {
+      $(context).find('.menu-link-form').drupalSetSummary(function (context) {
+        var $context = $(context);
+        if ($context.find('.js-form-item-menu-enabled input').is(':checked')) {
+          return Drupal.checkPlain($context.find('.js-form-item-menu-title input').val());
+        }
+        else {
+          return Drupal.t('Not in menu');
+        }
+      });
+    }
+  };
+
+  /**
+   * Automatically fill in a menu link title, if possible.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches change and keyup behavior for automatically filling out menu
+   *   link titles.
+   */
+  Drupal.behaviors.menuUiLinkAutomaticTitle = {
+    attach: function (context) {
+      var $context = $(context);
+      $context.find('.menu-link-form').each(function () {
+        var $this = $(this);
+        // Try to find menu settings widget elements as well as a 'title' field
+        // in the form, but play nicely with user permissions and form
+        // alterations.
+        var $checkbox = $this.find('.js-form-item-menu-enabled input');
+        var $link_title = $context.find('.js-form-item-menu-title input');
+        var $title = $this.closest('form').find('.js-form-item-title-0-value input');
+        // Bail out if we do not have all required fields.
+        if (!($checkbox.length && $link_title.length && $title.length)) {
+          return;
+        }
+        // If there is a link title already, mark it as overridden. The user
+        // expects that toggling the checkbox twice will take over the node's
+        // title.
+        if ($checkbox.is(':checked') && $link_title.val().length) {
+          $link_title.data('menuLinkAutomaticTitleOverridden', true);
+        }
+        // Whenever the value is changed manually, disable this behavior.
+        $link_title.on('keyup', function () {
+          $link_title.data('menuLinkAutomaticTitleOverridden', true);
+        });
+        // Global trigger on checkbox (do not fill-in a value when disabled).
+        $checkbox.on('change', function () {
+          if ($checkbox.is(':checked')) {
+            if (!$link_title.data('menuLinkAutomaticTitleOverridden')) {
+              $link_title.val($title.val());
+            }
+          }
+          else {
+            $link_title.val('');
+            $link_title.removeData('menuLinkAutomaticTitleOverridden');
+          }
+          $checkbox.closest('.vertical-tabs-pane').trigger('summaryUpdated');
+          $checkbox.trigger('formUpdated');
+        });
+        // Take over any title change.
+        $title.on('keyup', function () {
+          if (!$link_title.data('menuLinkAutomaticTitleOverridden') && $checkbox.is(':checked')) {
+            $link_title.val($title.val());
+            $link_title.val($title.val()).trigger('formUpdated');
+          }
+        });
+      });
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/core/modules/menu_ui/menu_ui.js b/core/modules/menu_ui/menu_ui.js
index 5eb10e0781c8..0d802148ba3b 100644
--- a/core/modules/menu_ui/menu_ui.js
+++ b/core/modules/menu_ui/menu_ui.js
@@ -1,83 +1,63 @@
 /**
- * @file
- * Menu UI behaviors.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/menu_ui/menu_ui.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   * Set a summary on the menu link form.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Find the form and call `drupalSetSummary` on it.
-   */
   Drupal.behaviors.menuUiDetailsSummaries = {
-    attach: function (context) {
+    attach: function attach(context) {
       $(context).find('.menu-link-form').drupalSetSummary(function (context) {
         var $context = $(context);
         if ($context.find('.js-form-item-menu-enabled input').is(':checked')) {
           return Drupal.checkPlain($context.find('.js-form-item-menu-title input').val());
-        }
-        else {
+        } else {
           return Drupal.t('Not in menu');
         }
       });
     }
   };
 
-  /**
-   * Automatically fill in a menu link title, if possible.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches change and keyup behavior for automatically filling out menu
-   *   link titles.
-   */
   Drupal.behaviors.menuUiLinkAutomaticTitle = {
-    attach: function (context) {
+    attach: function attach(context) {
       var $context = $(context);
       $context.find('.menu-link-form').each(function () {
         var $this = $(this);
-        // Try to find menu settings widget elements as well as a 'title' field
-        // in the form, but play nicely with user permissions and form
-        // alterations.
+
         var $checkbox = $this.find('.js-form-item-menu-enabled input');
         var $link_title = $context.find('.js-form-item-menu-title input');
         var $title = $this.closest('form').find('.js-form-item-title-0-value input');
-        // Bail out if we do not have all required fields.
+
         if (!($checkbox.length && $link_title.length && $title.length)) {
           return;
         }
-        // If there is a link title already, mark it as overridden. The user
-        // expects that toggling the checkbox twice will take over the node's
-        // title.
+
         if ($checkbox.is(':checked') && $link_title.val().length) {
           $link_title.data('menuLinkAutomaticTitleOverridden', true);
         }
-        // Whenever the value is changed manually, disable this behavior.
+
         $link_title.on('keyup', function () {
           $link_title.data('menuLinkAutomaticTitleOverridden', true);
         });
-        // Global trigger on checkbox (do not fill-in a value when disabled).
+
         $checkbox.on('change', function () {
           if ($checkbox.is(':checked')) {
             if (!$link_title.data('menuLinkAutomaticTitleOverridden')) {
               $link_title.val($title.val());
             }
-          }
-          else {
+          } else {
             $link_title.val('');
             $link_title.removeData('menuLinkAutomaticTitleOverridden');
           }
           $checkbox.closest('.vertical-tabs-pane').trigger('summaryUpdated');
           $checkbox.trigger('formUpdated');
         });
-        // Take over any title change.
+
         $title.on('keyup', function () {
           if (!$link_title.data('menuLinkAutomaticTitleOverridden') && $checkbox.is(':checked')) {
             $link_title.val($title.val());
@@ -87,5 +67,4 @@
       });
     }
   };
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/modules/node/content_types.es6.js b/core/modules/node/content_types.es6.js
new file mode 100644
index 000000000000..4ec34aa05e4f
--- /dev/null
+++ b/core/modules/node/content_types.es6.js
@@ -0,0 +1,62 @@
+/**
+ * @file
+ * Javascript for the node content editing form.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * Behaviors for setting summaries on content type form.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches summary behaviors on content type edit forms.
+   */
+  Drupal.behaviors.contentTypes = {
+    attach: function (context) {
+      var $context = $(context);
+      // Provide the vertical tab summaries.
+      $context.find('#edit-submission').drupalSetSummary(function (context) {
+        var vals = [];
+        vals.push(Drupal.checkPlain($(context).find('#edit-title-label').val()) || Drupal.t('Requires a title'));
+        return vals.join(', ');
+      });
+      $context.find('#edit-workflow').drupalSetSummary(function (context) {
+        var vals = [];
+        $(context).find('input[name^="options"]:checked').next('label').each(function () {
+          vals.push(Drupal.checkPlain($(this).text()));
+        });
+        if (!$(context).find('#edit-options-status').is(':checked')) {
+          vals.unshift(Drupal.t('Not published'));
+        }
+        return vals.join(', ');
+      });
+      $('#edit-language', context).drupalSetSummary(function (context) {
+        var vals = [];
+
+        vals.push($('.js-form-item-language-configuration-langcode select option:selected', context).text());
+
+        $('input:checked', context).next('label').each(function () {
+          vals.push(Drupal.checkPlain($(this).text()));
+        });
+
+        return vals.join(', ');
+      });
+      $context.find('#edit-display').drupalSetSummary(function (context) {
+        var vals = [];
+        var $editContext = $(context);
+        $editContext.find('input:checked').next('label').each(function () {
+          vals.push(Drupal.checkPlain($(this).text()));
+        });
+        if (!$editContext.find('#edit-display-submitted').is(':checked')) {
+          vals.unshift(Drupal.t("Don't display post information"));
+        }
+        return vals.join(', ');
+      });
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/core/modules/node/content_types.js b/core/modules/node/content_types.js
index 4ec34aa05e4f..832f17a97871 100644
--- a/core/modules/node/content_types.js
+++ b/core/modules/node/content_types.js
@@ -1,24 +1,19 @@
 /**
- * @file
- * Javascript for the node content editing form.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/node/content_types.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   * Behaviors for setting summaries on content type form.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches summary behaviors on content type edit forms.
-   */
   Drupal.behaviors.contentTypes = {
-    attach: function (context) {
+    attach: function attach(context) {
       var $context = $(context);
-      // Provide the vertical tab summaries.
+
       $context.find('#edit-submission').drupalSetSummary(function (context) {
         var vals = [];
         vals.push(Drupal.checkPlain($(context).find('#edit-title-label').val()) || Drupal.t('Requires a title'));
@@ -58,5 +53,4 @@
       });
     }
   };
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/modules/node/node.es6.js b/core/modules/node/node.es6.js
new file mode 100644
index 000000000000..086263d2a8e4
--- /dev/null
+++ b/core/modules/node/node.es6.js
@@ -0,0 +1,55 @@
+/**
+ * @file
+ * Defines Javascript behaviors for the node module.
+ */
+
+(function ($, Drupal, drupalSettings) {
+
+  'use strict';
+
+  /**
+   * Behaviors for tabs in the node edit form.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches summary behavior for tabs in the node edit form.
+   */
+  Drupal.behaviors.nodeDetailsSummaries = {
+    attach: function (context) {
+      var $context = $(context);
+
+      $context.find('.node-form-author').drupalSetSummary(function (context) {
+        var $authorContext = $(context);
+        var name = $authorContext.find('.field--name-uid input').val();
+        var date = $authorContext.find('.field--name-created input').val();
+
+        if (name && date) {
+          return Drupal.t('By @name on @date', {'@name': name, '@date': date});
+        }
+        else if (name) {
+          return Drupal.t('By @name', {'@name': name});
+        }
+        else if (date) {
+          return Drupal.t('Authored on @date', {'@date': date});
+        }
+      });
+
+      $context.find('.node-form-options').drupalSetSummary(function (context) {
+        var $optionsContext = $(context);
+        var vals = [];
+
+        if ($optionsContext.find('input').is(':checked')) {
+          $optionsContext.find('input:checked').next('label').each(function () {
+            vals.push(Drupal.checkPlain($.trim($(this).text())));
+          });
+          return vals.join(', ');
+        }
+        else {
+          return Drupal.t('Not promoted');
+        }
+      });
+    }
+  };
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/node/node.js b/core/modules/node/node.js
index 086263d2a8e4..76859aed54fe 100644
--- a/core/modules/node/node.js
+++ b/core/modules/node/node.js
@@ -1,22 +1,17 @@
 /**
- * @file
- * Defines Javascript behaviors for the node module.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/node/node.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings) {
 
   'use strict';
 
-  /**
-   * Behaviors for tabs in the node edit form.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches summary behavior for tabs in the node edit form.
-   */
   Drupal.behaviors.nodeDetailsSummaries = {
-    attach: function (context) {
+    attach: function attach(context) {
       var $context = $(context);
 
       $context.find('.node-form-author').drupalSetSummary(function (context) {
@@ -25,13 +20,11 @@
         var date = $authorContext.find('.field--name-created input').val();
 
         if (name && date) {
-          return Drupal.t('By @name on @date', {'@name': name, '@date': date});
-        }
-        else if (name) {
-          return Drupal.t('By @name', {'@name': name});
-        }
-        else if (date) {
-          return Drupal.t('Authored on @date', {'@date': date});
+          return Drupal.t('By @name on @date', { '@name': name, '@date': date });
+        } else if (name) {
+          return Drupal.t('By @name', { '@name': name });
+        } else if (date) {
+          return Drupal.t('Authored on @date', { '@date': date });
         }
       });
 
@@ -44,12 +37,10 @@
             vals.push(Drupal.checkPlain($.trim($(this).text())));
           });
           return vals.join(', ');
-        }
-        else {
+        } else {
           return Drupal.t('Not promoted');
         }
       });
     }
   };
-
-})(jQuery, Drupal, drupalSettings);
+})(jQuery, Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/modules/node/node.preview.es6.js b/core/modules/node/node.preview.es6.js
new file mode 100644
index 000000000000..4054183a1f75
--- /dev/null
+++ b/core/modules/node/node.preview.es6.js
@@ -0,0 +1,99 @@
+/**
+ * @file
+ * Preview behaviors.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * Disables all non-relevant links in node previews.
+   *
+   * Destroys links (except local fragment identifiers such as href="#frag") in
+   * node previews to prevent users from leaving the page.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches confirmation prompt for clicking links in node preview mode.
+   * @prop {Drupal~behaviorDetach} detach
+   *   Detaches confirmation prompt for clicking links in node preview mode.
+   */
+  Drupal.behaviors.nodePreviewDestroyLinks = {
+    attach: function (context) {
+
+      function clickPreviewModal(event) {
+        // Only confirm leaving previews when left-clicking and user is not
+        // pressing the ALT, CTRL, META (Command key on the Macintosh keyboard)
+        // or SHIFT key.
+        if (event.button === 0 && !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey) {
+          event.preventDefault();
+          var $previewDialog = $('<div>' + Drupal.theme('nodePreviewModal') + '</div>').appendTo('body');
+          Drupal.dialog($previewDialog, {
+            title: Drupal.t('Leave preview?'),
+            buttons: [
+              {
+                text: Drupal.t('Cancel'),
+                click: function () {
+                  $(this).dialog('close');
+                }
+              }, {
+                text: Drupal.t('Leave preview'),
+                click: function () {
+                  window.top.location.href = event.target.href;
+                }
+              }
+            ]
+          }).showModal();
+        }
+      }
+
+      var $preview = $(context).find('.content').once('node-preview');
+      if ($(context).find('.node-preview-container').length) {
+        $preview.on('click.preview', 'a:not([href^=#], #edit-backlink, #toolbar-administration a)', clickPreviewModal);
+      }
+    },
+    detach: function (context, settings, trigger) {
+      if (trigger === 'unload') {
+        var $preview = $(context).find('.content').removeOnce('node-preview');
+        if ($preview.length) {
+          $preview.off('click.preview');
+        }
+      }
+    }
+  };
+
+  /**
+   * Switch view mode.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches automatic submit on `formUpdated.preview` events.
+   */
+  Drupal.behaviors.nodePreviewSwitchViewMode = {
+    attach: function (context) {
+      var $autosubmit = $(context).find('[data-drupal-autosubmit]').once('autosubmit');
+      if ($autosubmit.length) {
+        $autosubmit.on('formUpdated.preview', function () {
+          $(this.form).trigger('submit');
+        });
+      }
+    }
+  };
+
+  /**
+   * Theme function for node preview modal.
+   *
+   * @return {string}
+   *   Markup for the node preview modal.
+   */
+  Drupal.theme.nodePreviewModal = function () {
+    return '<p>' +
+      Drupal.t('Leaving the preview will cause unsaved changes to be lost. Are you sure you want to leave the preview?') +
+      '</p><small class="description">' +
+      Drupal.t('CTRL+Left click will prevent this dialog from showing and proceed to the clicked link.') + '</small>';
+  };
+
+})(jQuery, Drupal);
diff --git a/core/modules/node/node.preview.js b/core/modules/node/node.preview.js
index 4054183a1f75..1ac13fefa58a 100644
--- a/core/modules/node/node.preview.js
+++ b/core/modules/node/node.preview.js
@@ -1,50 +1,35 @@
 /**
- * @file
- * Preview behaviors.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/node/node.preview.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   * Disables all non-relevant links in node previews.
-   *
-   * Destroys links (except local fragment identifiers such as href="#frag") in
-   * node previews to prevent users from leaving the page.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches confirmation prompt for clicking links in node preview mode.
-   * @prop {Drupal~behaviorDetach} detach
-   *   Detaches confirmation prompt for clicking links in node preview mode.
-   */
   Drupal.behaviors.nodePreviewDestroyLinks = {
-    attach: function (context) {
+    attach: function attach(context) {
 
       function clickPreviewModal(event) {
-        // Only confirm leaving previews when left-clicking and user is not
-        // pressing the ALT, CTRL, META (Command key on the Macintosh keyboard)
-        // or SHIFT key.
         if (event.button === 0 && !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey) {
           event.preventDefault();
           var $previewDialog = $('<div>' + Drupal.theme('nodePreviewModal') + '</div>').appendTo('body');
           Drupal.dialog($previewDialog, {
             title: Drupal.t('Leave preview?'),
-            buttons: [
-              {
-                text: Drupal.t('Cancel'),
-                click: function () {
-                  $(this).dialog('close');
-                }
-              }, {
-                text: Drupal.t('Leave preview'),
-                click: function () {
-                  window.top.location.href = event.target.href;
-                }
+            buttons: [{
+              text: Drupal.t('Cancel'),
+              click: function click() {
+                $(this).dialog('close');
               }
-            ]
+            }, {
+              text: Drupal.t('Leave preview'),
+              click: function click() {
+                window.top.location.href = event.target.href;
+              }
+            }]
           }).showModal();
         }
       }
@@ -54,7 +39,7 @@
         $preview.on('click.preview', 'a:not([href^=#], #edit-backlink, #toolbar-administration a)', clickPreviewModal);
       }
     },
-    detach: function (context, settings, trigger) {
+    detach: function detach(context, settings, trigger) {
       if (trigger === 'unload') {
         var $preview = $(context).find('.content').removeOnce('node-preview');
         if ($preview.length) {
@@ -64,16 +49,8 @@
     }
   };
 
-  /**
-   * Switch view mode.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches automatic submit on `formUpdated.preview` events.
-   */
   Drupal.behaviors.nodePreviewSwitchViewMode = {
-    attach: function (context) {
+    attach: function attach(context) {
       var $autosubmit = $(context).find('[data-drupal-autosubmit]').once('autosubmit');
       if ($autosubmit.length) {
         $autosubmit.on('formUpdated.preview', function () {
@@ -83,17 +60,7 @@
     }
   };
 
-  /**
-   * Theme function for node preview modal.
-   *
-   * @return {string}
-   *   Markup for the node preview modal.
-   */
   Drupal.theme.nodePreviewModal = function () {
-    return '<p>' +
-      Drupal.t('Leaving the preview will cause unsaved changes to be lost. Are you sure you want to leave the preview?') +
-      '</p><small class="description">' +
-      Drupal.t('CTRL+Left click will prevent this dialog from showing and proceed to the clicked link.') + '</small>';
+    return '<p>' + Drupal.t('Leaving the preview will cause unsaved changes to be lost. Are you sure you want to leave the preview?') + '</p><small class="description">' + Drupal.t('CTRL+Left click will prevent this dialog from showing and proceed to the clicked link.') + '</small>';
   };
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/modules/outside_in/js/off-canvas.es6.js b/core/modules/outside_in/js/off-canvas.es6.js
new file mode 100644
index 000000000000..d7c8d906f6ba
--- /dev/null
+++ b/core/modules/outside_in/js/off-canvas.es6.js
@@ -0,0 +1,160 @@
+/**
+ * @file
+ * Drupal's off-canvas library.
+ *
+ * @todo This functionality should extracted into a new core library or a part
+ *  of the current drupal.dialog.ajax library.
+ *  https://www.drupal.org/node/2784443
+ */
+
+(function ($, Drupal, debounce, displace) {
+
+  'use strict';
+
+  // The minimum width to use body displace needs to match the width at which
+  // the tray will be %100 width. @see outside_in.module.css
+  var minDisplaceWidth = 768;
+
+  /**
+   * The edge of the screen that the dialog should appear on.
+   *
+   * @type {string}
+   */
+  var edge = document.documentElement.dir === 'rtl' ? 'left' : 'right';
+
+  var $mainCanvasWrapper = $('[data-off-canvas-main-canvas]');
+
+  /**
+   * Resets the size of the dialog.
+   *
+   * @param {jQuery.Event} event
+   *   The event triggered.
+   */
+  function resetSize(event) {
+    var offsets = displace.offsets;
+    var $element = event.data.$element;
+    var $widget = $element.dialog('widget');
+
+    var adjustedOptions = {
+      // @see http://api.jqueryui.com/position/
+      position: {
+        my: edge + ' top',
+        at: edge + ' top' + (offsets.top !== 0 ? '+' + offsets.top : ''),
+        of: window
+      }
+    };
+
+    $widget.css({
+      position: 'fixed',
+      height: ($(window).height() - (offsets.top + offsets.bottom)) + 'px'
+    });
+
+    $element
+      .dialog('option', adjustedOptions)
+      .trigger('dialogContentResize.off-canvas');
+  }
+
+  /**
+   * Adjusts the dialog on resize.
+   *
+   * @param {jQuery.Event} event
+   *   The event triggered.
+   */
+  function handleDialogResize(event) {
+    var $element = event.data.$element;
+    var $widget = $element.dialog('widget');
+
+    var $offsets = $widget.find('> :not(#drupal-off-canvas, .ui-resizable-handle)');
+    var offset = 0;
+    var modalHeight;
+
+    // Let scroll element take all the height available.
+    $element.css({height: 'auto'});
+    modalHeight = $widget.height();
+    $offsets.each(function () { offset += $(this).outerHeight(); });
+
+    // Take internal padding into account.
+    var scrollOffset = $element.outerHeight() - $element.height();
+    $element.height(modalHeight - offset - scrollOffset);
+  }
+
+  /**
+   * Adjusts the body padding when the dialog is resized.
+   *
+   * @param {jQuery.Event} event
+   *   The event triggered.
+   */
+  function bodyPadding(event) {
+    if ($('body').outerWidth() < minDisplaceWidth) {
+      return;
+    }
+    var $element = event.data.$element;
+    var $widget = $element.dialog('widget');
+
+    var width = $widget.outerWidth();
+    var mainCanvasPadding = $mainCanvasWrapper.css('padding-' + edge);
+    if (width !== mainCanvasPadding) {
+      $mainCanvasWrapper.css('padding-' + edge, width + 'px');
+      $widget.attr('data-offset-' + edge, width);
+      displace();
+    }
+  }
+
+  /**
+   * Attaches off-canvas dialog behaviors.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches event listeners for off-canvas dialogs.
+   */
+  Drupal.behaviors.offCanvasEvents = {
+    attach: function () {
+      $(window).once('off-canvas').on({
+        'dialog:aftercreate': function (event, dialog, $element, settings) {
+          if ($element.is('#drupal-off-canvas')) {
+            var eventData = {settings: settings, $element: $element};
+            $('.ui-dialog-off-canvas, .ui-dialog-off-canvas .ui-dialog-titlebar').toggleClass('ui-dialog-empty-title', !settings.title);
+
+            $element
+              .on('dialogresize.off-canvas', eventData, debounce(bodyPadding, 100))
+              .on('dialogContentResize.off-canvas', eventData, handleDialogResize)
+              .on('dialogContentResize.off-canvas', eventData, debounce(bodyPadding, 100))
+              .trigger('dialogresize.off-canvas');
+
+            $element.dialog('widget').attr('data-offset-' + edge, '');
+
+            $(window)
+              .on('resize.off-canvas scroll.off-canvas', eventData, debounce(resetSize, 100))
+              .trigger('resize.off-canvas');
+          }
+        },
+        'dialog:beforecreate': function (event, dialog, $element, settings) {
+          if ($element.is('#drupal-off-canvas')) {
+            $('body').addClass('js-tray-open');
+            // @see http://api.jqueryui.com/position/
+            settings.position = {
+              my: 'left top',
+              at: edge + ' top',
+              of: window
+            };
+            settings.dialogClass += ' ui-dialog-off-canvas';
+            // Applies initial height to dialog based on window height.
+            // See http://api.jqueryui.com/dialog for all dialog options.
+            settings.height = $(window).height();
+          }
+        },
+        'dialog:beforeclose': function (event, dialog, $element) {
+          if ($element.is('#drupal-off-canvas')) {
+            $('body').removeClass('js-tray-open');
+            // Remove all *.off-canvas events
+            $(document).off('.off-canvas');
+            $(window).off('.off-canvas');
+            $mainCanvasWrapper.css('padding-' + edge, 0);
+          }
+        }
+      });
+    }
+  };
+
+})(jQuery, Drupal, Drupal.debounce, Drupal.displace);
diff --git a/core/modules/outside_in/js/off-canvas.js b/core/modules/outside_in/js/off-canvas.js
index d7c8d906f6ba..c52e699058df 100644
--- a/core/modules/outside_in/js/off-canvas.js
+++ b/core/modules/outside_in/js/off-canvas.js
@@ -1,42 +1,27 @@
 /**
- * @file
- * Drupal's off-canvas library.
- *
- * @todo This functionality should extracted into a new core library or a part
- *  of the current drupal.dialog.ajax library.
- *  https://www.drupal.org/node/2784443
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/outside_in/js/off-canvas.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, debounce, displace) {
 
   'use strict';
 
-  // The minimum width to use body displace needs to match the width at which
-  // the tray will be %100 width. @see outside_in.module.css
   var minDisplaceWidth = 768;
 
-  /**
-   * The edge of the screen that the dialog should appear on.
-   *
-   * @type {string}
-   */
   var edge = document.documentElement.dir === 'rtl' ? 'left' : 'right';
 
   var $mainCanvasWrapper = $('[data-off-canvas-main-canvas]');
 
-  /**
-   * Resets the size of the dialog.
-   *
-   * @param {jQuery.Event} event
-   *   The event triggered.
-   */
   function resetSize(event) {
     var offsets = displace.offsets;
     var $element = event.data.$element;
     var $widget = $element.dialog('widget');
 
     var adjustedOptions = {
-      // @see http://api.jqueryui.com/position/
       position: {
         my: edge + ' top',
         at: edge + ' top' + (offsets.top !== 0 ? '+' + offsets.top : ''),
@@ -46,20 +31,12 @@
 
     $widget.css({
       position: 'fixed',
-      height: ($(window).height() - (offsets.top + offsets.bottom)) + 'px'
+      height: $(window).height() - (offsets.top + offsets.bottom) + 'px'
     });
 
-    $element
-      .dialog('option', adjustedOptions)
-      .trigger('dialogContentResize.off-canvas');
+    $element.dialog('option', adjustedOptions).trigger('dialogContentResize.off-canvas');
   }
 
-  /**
-   * Adjusts the dialog on resize.
-   *
-   * @param {jQuery.Event} event
-   *   The event triggered.
-   */
   function handleDialogResize(event) {
     var $element = event.data.$element;
     var $widget = $element.dialog('widget');
@@ -68,22 +45,16 @@
     var offset = 0;
     var modalHeight;
 
-    // Let scroll element take all the height available.
-    $element.css({height: 'auto'});
+    $element.css({ height: 'auto' });
     modalHeight = $widget.height();
-    $offsets.each(function () { offset += $(this).outerHeight(); });
+    $offsets.each(function () {
+      offset += $(this).outerHeight();
+    });
 
-    // Take internal padding into account.
     var scrollOffset = $element.outerHeight() - $element.height();
     $element.height(modalHeight - offset - scrollOffset);
   }
 
-  /**
-   * Adjusts the body padding when the dialog is resized.
-   *
-   * @param {jQuery.Event} event
-   *   The event triggered.
-   */
   function bodyPadding(event) {
     if ($('body').outerWidth() < minDisplaceWidth) {
       return;
@@ -100,54 +71,39 @@
     }
   }
 
-  /**
-   * Attaches off-canvas dialog behaviors.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches event listeners for off-canvas dialogs.
-   */
   Drupal.behaviors.offCanvasEvents = {
-    attach: function () {
+    attach: function attach() {
       $(window).once('off-canvas').on({
-        'dialog:aftercreate': function (event, dialog, $element, settings) {
+        'dialog:aftercreate': function dialogAftercreate(event, dialog, $element, settings) {
           if ($element.is('#drupal-off-canvas')) {
-            var eventData = {settings: settings, $element: $element};
+            var eventData = { settings: settings, $element: $element };
             $('.ui-dialog-off-canvas, .ui-dialog-off-canvas .ui-dialog-titlebar').toggleClass('ui-dialog-empty-title', !settings.title);
 
-            $element
-              .on('dialogresize.off-canvas', eventData, debounce(bodyPadding, 100))
-              .on('dialogContentResize.off-canvas', eventData, handleDialogResize)
-              .on('dialogContentResize.off-canvas', eventData, debounce(bodyPadding, 100))
-              .trigger('dialogresize.off-canvas');
+            $element.on('dialogresize.off-canvas', eventData, debounce(bodyPadding, 100)).on('dialogContentResize.off-canvas', eventData, handleDialogResize).on('dialogContentResize.off-canvas', eventData, debounce(bodyPadding, 100)).trigger('dialogresize.off-canvas');
 
             $element.dialog('widget').attr('data-offset-' + edge, '');
 
-            $(window)
-              .on('resize.off-canvas scroll.off-canvas', eventData, debounce(resetSize, 100))
-              .trigger('resize.off-canvas');
+            $(window).on('resize.off-canvas scroll.off-canvas', eventData, debounce(resetSize, 100)).trigger('resize.off-canvas');
           }
         },
-        'dialog:beforecreate': function (event, dialog, $element, settings) {
+        'dialog:beforecreate': function dialogBeforecreate(event, dialog, $element, settings) {
           if ($element.is('#drupal-off-canvas')) {
             $('body').addClass('js-tray-open');
-            // @see http://api.jqueryui.com/position/
+
             settings.position = {
               my: 'left top',
               at: edge + ' top',
               of: window
             };
             settings.dialogClass += ' ui-dialog-off-canvas';
-            // Applies initial height to dialog based on window height.
-            // See http://api.jqueryui.com/dialog for all dialog options.
+
             settings.height = $(window).height();
           }
         },
-        'dialog:beforeclose': function (event, dialog, $element) {
+        'dialog:beforeclose': function dialogBeforeclose(event, dialog, $element) {
           if ($element.is('#drupal-off-canvas')) {
             $('body').removeClass('js-tray-open');
-            // Remove all *.off-canvas events
+
             $(document).off('.off-canvas');
             $(window).off('.off-canvas');
             $mainCanvasWrapper.css('padding-' + edge, 0);
@@ -156,5 +112,4 @@
       });
     }
   };
-
-})(jQuery, Drupal, Drupal.debounce, Drupal.displace);
+})(jQuery, Drupal, Drupal.debounce, Drupal.displace);
\ No newline at end of file
diff --git a/core/modules/outside_in/js/outside_in.es6.js b/core/modules/outside_in/js/outside_in.es6.js
new file mode 100644
index 000000000000..eea9075cab52
--- /dev/null
+++ b/core/modules/outside_in/js/outside_in.es6.js
@@ -0,0 +1,265 @@
+/**
+ * @file
+ * Drupal's Settings Tray library.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  var blockConfigureSelector = '[data-outside-in-edit]';
+  var toggleEditSelector = '[data-drupal-outsidein="toggle"]';
+  var itemsToToggleSelector = '[data-off-canvas-main-canvas], #toolbar-bar, [data-drupal-outsidein="editable"] a, [data-drupal-outsidein="editable"] button';
+  var contextualItemsSelector = '[data-contextual-id] a, [data-contextual-id] button';
+  var quickEditItemSelector = '[data-quickedit-entity-id]';
+
+  /**
+   * Reacts to contextual links being added.
+   *
+   * @param {jQuery.Event} event
+   *   The `drupalContextualLinkAdded` event.
+   * @param {object} data
+   *   An object containing the data relevant to the event.
+   *
+   * @listens event:drupalContextualLinkAdded
+   */
+  $(document).on('drupalContextualLinkAdded', function (event, data) {
+    // Bind Ajax behaviors to all items showing the class.
+    // @todo Fix contextual links to work with use-ajax links in
+    //    https://www.drupal.org/node/2764931.
+    Drupal.attachBehaviors(data.$el[0]);
+
+    // Bind a listener to all 'Quick edit' links for blocks
+    // Click "Edit" button in toolbar to force Contextual Edit which starts
+    // Settings Tray edit mode also.
+    data.$el.find(blockConfigureSelector)
+      .on('click.outsidein', function () {
+        if (!isInEditMode()) {
+          $(toggleEditSelector).trigger('click').trigger('click.outside_in');
+        }
+        // Always disable QuickEdit regardless of whether "EditMode" was just enabled.
+        disableQuickEdit();
+      });
+  });
+
+  $(document).on('keyup.outsidein', function (e) {
+    if (isInEditMode() && e.keyCode === 27) {
+      Drupal.announce(
+        Drupal.t('Exited edit mode.')
+      );
+      toggleEditMode();
+    }
+  });
+
+  /**
+   * Gets all items that should be toggled with class during edit mode.
+   *
+   * @return {jQuery}
+   *   Items that should be toggled.
+   */
+  function getItemsToToggle() {
+    return $(itemsToToggleSelector).not(contextualItemsSelector);
+  }
+
+  /**
+   * Helper to check the state of the outside-in mode.
+   *
+   * @todo don't use a class for this.
+   *
+   * @return {boolean}
+   *  State of the outside-in edit mode.
+   */
+  function isInEditMode() {
+    return $('#toolbar-bar').hasClass('js-outside-in-edit-mode');
+  }
+
+  /**
+   * Helper to toggle Edit mode.
+   */
+  function toggleEditMode() {
+    setEditModeState(!isInEditMode());
+  }
+
+  /**
+   * Prevent default click events except contextual links.
+   *
+   * In edit mode the default action of click events is suppressed.
+   *
+   * @param {jQuery.Event} event
+   *   The click event.
+   */
+  function preventClick(event) {
+    // Do not prevent contextual links.
+    if ($(event.target).closest('.contextual-links').length) {
+      return;
+    }
+    event.preventDefault();
+  }
+
+  /**
+   * Close any active toolbar tray before entering edit mode.
+   */
+  function closeToolbarTrays() {
+    $(Drupal.toolbar.models.toolbarModel.get('activeTab')).trigger('click');
+  }
+
+  /**
+   * Disables the QuickEdit module editor if open.
+   */
+  function disableQuickEdit() {
+    $('.quickedit-toolbar button.action-cancel').trigger('click');
+  }
+
+  /**
+   * Closes/removes off-canvas.
+   */
+  function closeOffCanvas() {
+    $('.ui-dialog-off-canvas .ui-dialog-titlebar-close').trigger('click');
+  }
+
+  /**
+   *  Helper to switch edit mode state.
+   *
+   * @param {boolean} editMode
+   *  True enable edit mode, false disable edit mode.
+   */
+  function setEditModeState(editMode) {
+    if (!document.querySelector('[data-off-canvas-main-canvas]')) {
+      throw new Error('data-off-canvas-main-canvas is missing from outside-in-page-wrapper.html.twig');
+    }
+    editMode = !!editMode;
+    var $editButton = $(toggleEditSelector);
+    var $editables;
+    // Turn on edit mode.
+    if (editMode) {
+      $editButton.text(Drupal.t('Editing'));
+      closeToolbarTrays();
+
+      $editables = $('[data-drupal-outsidein="editable"]').once('outsidein');
+      if ($editables.length) {
+        // Use event capture to prevent clicks on links.
+        document.querySelector('[data-off-canvas-main-canvas]').addEventListener('click', preventClick, true);
+
+        // When a click occurs try and find the outside-in edit link
+        // and click it.
+        $editables
+          .not(contextualItemsSelector)
+          .on('click.outsidein', function (e) {
+            // Contextual links are allowed to function in Edit mode.
+            if ($(e.target).closest('.contextual').length || !localStorage.getItem('Drupal.contextualToolbar.isViewing')) {
+              return;
+            }
+            $(e.currentTarget).find(blockConfigureSelector).trigger('click');
+            disableQuickEdit();
+          });
+        $(quickEditItemSelector)
+          .not(contextualItemsSelector)
+          .on('click.outsidein', function (e) {
+            // For all non-contextual links or the contextual QuickEdit link close the off-canvas dialog.
+            if (!$(e.target).parent().hasClass('contextual') || $(e.target).parent().hasClass('quickedit')) {
+              closeOffCanvas();
+            }
+            // Do not trigger if target is quick edit link to avoid loop.
+            if ($(e.target).parent().hasClass('contextual') || $(e.target).parent().hasClass('quickedit')) {
+              return;
+            }
+            $(e.currentTarget).find('li.quickedit a').trigger('click');
+          });
+      }
+    }
+    // Disable edit mode.
+    else {
+      $editables = $('[data-drupal-outsidein="editable"]').removeOnce('outsidein');
+      if ($editables.length) {
+        document.querySelector('[data-off-canvas-main-canvas]').removeEventListener('click', preventClick, true);
+        $editables.off('.outsidein');
+        $(quickEditItemSelector).off('.outsidein');
+      }
+
+      $editButton.text(Drupal.t('Edit'));
+      closeOffCanvas();
+      disableQuickEdit();
+    }
+    getItemsToToggle().toggleClass('js-outside-in-edit-mode', editMode);
+    $('.edit-mode-inactive').toggleClass('visually-hidden', editMode);
+  }
+
+  /**
+   * Attaches contextual's edit toolbar tab behavior.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches contextual toolbar behavior on a contextualToolbar-init event.
+   */
+  Drupal.behaviors.outsideInEdit = {
+    attach: function () {
+      var editMode = localStorage.getItem('Drupal.contextualToolbar.isViewing') === 'false';
+      if (editMode) {
+        setEditModeState(true);
+      }
+    }
+  };
+
+  /**
+   * Toggle the js-outside-edit-mode class on items that we want to disable while in edit mode.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Toggle the js-outside-edit-mode class.
+   */
+  Drupal.behaviors.toggleEditMode = {
+    attach: function () {
+
+      $(toggleEditSelector).once('outsidein').on('click.outsidein', toggleEditMode);
+
+      var search = Drupal.ajax.WRAPPER_FORMAT + '=drupal_dialog';
+      var replace = Drupal.ajax.WRAPPER_FORMAT + '=drupal_dialog_off_canvas';
+      // Loop through all Ajax links and change the format to dialog-off-canvas when
+      // needed.
+      Drupal.ajax.instances
+        .filter(function (instance) {
+          var hasElement = instance && !!instance.element;
+          var rendererOffCanvas = false;
+          var wrapperOffCanvas = false;
+          if (hasElement) {
+            rendererOffCanvas = $(instance.element).attr('data-dialog-renderer') === 'off_canvas';
+            wrapperOffCanvas = instance.options.url.indexOf('drupal_dialog_off_canvas') === -1;
+          }
+          return hasElement && rendererOffCanvas && wrapperOffCanvas;
+        })
+        .forEach(function (instance) {
+          // @todo Move logic for data-dialog-renderer attribute into ajax.js
+          //   https://www.drupal.org/node/2784443
+          instance.options.url = instance.options.url.replace(search, replace);
+          // Check to make sure existing dialogOptions aren't overridden.
+          if (!('dialogOptions' in instance.options.data)) {
+            instance.options.data.dialogOptions = {};
+          }
+          instance.options.data.dialogOptions.outsideInActiveEditableId = $(instance.element).parents('.outside-in-editable').attr('id');
+          instance.progress = {type: 'fullscreen'};
+        });
+    }
+  };
+
+  // Manage Active editable class on opening and closing of the dialog.
+  $(window).on({
+    'dialog:beforecreate': function (event, dialog, $element, settings) {
+      if ($element.is('#drupal-off-canvas')) {
+        $('body .outside-in-active-editable').removeClass('outside-in-active-editable');
+        var $activeElement = $('#' + settings.outsideInActiveEditableId);
+        if ($activeElement.length) {
+          $activeElement.addClass('outside-in-active-editable');
+          settings.dialogClass += ' ui-dialog-outside-in';
+        }
+      }
+    },
+    'dialog:beforeclose': function (event, dialog, $element) {
+      if ($element.is('#drupal-off-canvas')) {
+        $('body .outside-in-active-editable').removeClass('outside-in-active-editable');
+      }
+    }
+  });
+
+})(jQuery, Drupal);
diff --git a/core/modules/outside_in/js/outside_in.js b/core/modules/outside_in/js/outside_in.js
index eea9075cab52..a28bd81406c3 100644
--- a/core/modules/outside_in/js/outside_in.js
+++ b/core/modules/outside_in/js/outside_in.js
@@ -1,7 +1,10 @@
 /**
- * @file
- * Drupal's Settings Tray library.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/outside_in/js/outside_in.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
@@ -13,116 +16,56 @@
   var contextualItemsSelector = '[data-contextual-id] a, [data-contextual-id] button';
   var quickEditItemSelector = '[data-quickedit-entity-id]';
 
-  /**
-   * Reacts to contextual links being added.
-   *
-   * @param {jQuery.Event} event
-   *   The `drupalContextualLinkAdded` event.
-   * @param {object} data
-   *   An object containing the data relevant to the event.
-   *
-   * @listens event:drupalContextualLinkAdded
-   */
   $(document).on('drupalContextualLinkAdded', function (event, data) {
-    // Bind Ajax behaviors to all items showing the class.
-    // @todo Fix contextual links to work with use-ajax links in
-    //    https://www.drupal.org/node/2764931.
     Drupal.attachBehaviors(data.$el[0]);
 
-    // Bind a listener to all 'Quick edit' links for blocks
-    // Click "Edit" button in toolbar to force Contextual Edit which starts
-    // Settings Tray edit mode also.
-    data.$el.find(blockConfigureSelector)
-      .on('click.outsidein', function () {
-        if (!isInEditMode()) {
-          $(toggleEditSelector).trigger('click').trigger('click.outside_in');
-        }
-        // Always disable QuickEdit regardless of whether "EditMode" was just enabled.
-        disableQuickEdit();
-      });
+    data.$el.find(blockConfigureSelector).on('click.outsidein', function () {
+      if (!isInEditMode()) {
+        $(toggleEditSelector).trigger('click').trigger('click.outside_in');
+      }
+
+      disableQuickEdit();
+    });
   });
 
   $(document).on('keyup.outsidein', function (e) {
     if (isInEditMode() && e.keyCode === 27) {
-      Drupal.announce(
-        Drupal.t('Exited edit mode.')
-      );
+      Drupal.announce(Drupal.t('Exited edit mode.'));
       toggleEditMode();
     }
   });
 
-  /**
-   * Gets all items that should be toggled with class during edit mode.
-   *
-   * @return {jQuery}
-   *   Items that should be toggled.
-   */
   function getItemsToToggle() {
     return $(itemsToToggleSelector).not(contextualItemsSelector);
   }
 
-  /**
-   * Helper to check the state of the outside-in mode.
-   *
-   * @todo don't use a class for this.
-   *
-   * @return {boolean}
-   *  State of the outside-in edit mode.
-   */
   function isInEditMode() {
     return $('#toolbar-bar').hasClass('js-outside-in-edit-mode');
   }
 
-  /**
-   * Helper to toggle Edit mode.
-   */
   function toggleEditMode() {
     setEditModeState(!isInEditMode());
   }
 
-  /**
-   * Prevent default click events except contextual links.
-   *
-   * In edit mode the default action of click events is suppressed.
-   *
-   * @param {jQuery.Event} event
-   *   The click event.
-   */
   function preventClick(event) {
-    // Do not prevent contextual links.
     if ($(event.target).closest('.contextual-links').length) {
       return;
     }
     event.preventDefault();
   }
 
-  /**
-   * Close any active toolbar tray before entering edit mode.
-   */
   function closeToolbarTrays() {
     $(Drupal.toolbar.models.toolbarModel.get('activeTab')).trigger('click');
   }
 
-  /**
-   * Disables the QuickEdit module editor if open.
-   */
   function disableQuickEdit() {
     $('.quickedit-toolbar button.action-cancel').trigger('click');
   }
 
-  /**
-   * Closes/removes off-canvas.
-   */
   function closeOffCanvas() {
     $('.ui-dialog-off-canvas .ui-dialog-titlebar-close').trigger('click');
   }
 
-  /**
-   *  Helper to switch edit mode state.
-   *
-   * @param {boolean} editMode
-   *  True enable edit mode, false disable edit mode.
-   */
   function setEditModeState(editMode) {
     if (!document.querySelector('[data-off-canvas-main-canvas]')) {
       throw new Error('data-off-canvas-main-canvas is missing from outside-in-page-wrapper.html.twig');
@@ -130,70 +73,51 @@
     editMode = !!editMode;
     var $editButton = $(toggleEditSelector);
     var $editables;
-    // Turn on edit mode.
+
     if (editMode) {
       $editButton.text(Drupal.t('Editing'));
       closeToolbarTrays();
 
       $editables = $('[data-drupal-outsidein="editable"]').once('outsidein');
       if ($editables.length) {
-        // Use event capture to prevent clicks on links.
         document.querySelector('[data-off-canvas-main-canvas]').addEventListener('click', preventClick, true);
 
-        // When a click occurs try and find the outside-in edit link
-        // and click it.
-        $editables
-          .not(contextualItemsSelector)
-          .on('click.outsidein', function (e) {
-            // Contextual links are allowed to function in Edit mode.
-            if ($(e.target).closest('.contextual').length || !localStorage.getItem('Drupal.contextualToolbar.isViewing')) {
-              return;
-            }
-            $(e.currentTarget).find(blockConfigureSelector).trigger('click');
-            disableQuickEdit();
-          });
-        $(quickEditItemSelector)
-          .not(contextualItemsSelector)
-          .on('click.outsidein', function (e) {
-            // For all non-contextual links or the contextual QuickEdit link close the off-canvas dialog.
-            if (!$(e.target).parent().hasClass('contextual') || $(e.target).parent().hasClass('quickedit')) {
-              closeOffCanvas();
-            }
-            // Do not trigger if target is quick edit link to avoid loop.
-            if ($(e.target).parent().hasClass('contextual') || $(e.target).parent().hasClass('quickedit')) {
-              return;
-            }
-            $(e.currentTarget).find('li.quickedit a').trigger('click');
-          });
-      }
-    }
-    // Disable edit mode.
-    else {
-      $editables = $('[data-drupal-outsidein="editable"]').removeOnce('outsidein');
-      if ($editables.length) {
-        document.querySelector('[data-off-canvas-main-canvas]').removeEventListener('click', preventClick, true);
-        $editables.off('.outsidein');
-        $(quickEditItemSelector).off('.outsidein');
+        $editables.not(contextualItemsSelector).on('click.outsidein', function (e) {
+          if ($(e.target).closest('.contextual').length || !localStorage.getItem('Drupal.contextualToolbar.isViewing')) {
+            return;
+          }
+          $(e.currentTarget).find(blockConfigureSelector).trigger('click');
+          disableQuickEdit();
+        });
+        $(quickEditItemSelector).not(contextualItemsSelector).on('click.outsidein', function (e) {
+          if (!$(e.target).parent().hasClass('contextual') || $(e.target).parent().hasClass('quickedit')) {
+            closeOffCanvas();
+          }
+
+          if ($(e.target).parent().hasClass('contextual') || $(e.target).parent().hasClass('quickedit')) {
+            return;
+          }
+          $(e.currentTarget).find('li.quickedit a').trigger('click');
+        });
       }
+    } else {
+        $editables = $('[data-drupal-outsidein="editable"]').removeOnce('outsidein');
+        if ($editables.length) {
+          document.querySelector('[data-off-canvas-main-canvas]').removeEventListener('click', preventClick, true);
+          $editables.off('.outsidein');
+          $(quickEditItemSelector).off('.outsidein');
+        }
 
-      $editButton.text(Drupal.t('Edit'));
-      closeOffCanvas();
-      disableQuickEdit();
-    }
+        $editButton.text(Drupal.t('Edit'));
+        closeOffCanvas();
+        disableQuickEdit();
+      }
     getItemsToToggle().toggleClass('js-outside-in-edit-mode', editMode);
     $('.edit-mode-inactive').toggleClass('visually-hidden', editMode);
   }
 
-  /**
-   * Attaches contextual's edit toolbar tab behavior.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches contextual toolbar behavior on a contextualToolbar-init event.
-   */
   Drupal.behaviors.outsideInEdit = {
-    attach: function () {
+    attach: function attach() {
       var editMode = localStorage.getItem('Drupal.contextualToolbar.isViewing') === 'false';
       if (editMode) {
         setEditModeState(true);
@@ -201,51 +125,37 @@
     }
   };
 
-  /**
-   * Toggle the js-outside-edit-mode class on items that we want to disable while in edit mode.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Toggle the js-outside-edit-mode class.
-   */
   Drupal.behaviors.toggleEditMode = {
-    attach: function () {
+    attach: function attach() {
 
       $(toggleEditSelector).once('outsidein').on('click.outsidein', toggleEditMode);
 
       var search = Drupal.ajax.WRAPPER_FORMAT + '=drupal_dialog';
       var replace = Drupal.ajax.WRAPPER_FORMAT + '=drupal_dialog_off_canvas';
-      // Loop through all Ajax links and change the format to dialog-off-canvas when
-      // needed.
-      Drupal.ajax.instances
-        .filter(function (instance) {
-          var hasElement = instance && !!instance.element;
-          var rendererOffCanvas = false;
-          var wrapperOffCanvas = false;
-          if (hasElement) {
-            rendererOffCanvas = $(instance.element).attr('data-dialog-renderer') === 'off_canvas';
-            wrapperOffCanvas = instance.options.url.indexOf('drupal_dialog_off_canvas') === -1;
-          }
-          return hasElement && rendererOffCanvas && wrapperOffCanvas;
-        })
-        .forEach(function (instance) {
-          // @todo Move logic for data-dialog-renderer attribute into ajax.js
-          //   https://www.drupal.org/node/2784443
-          instance.options.url = instance.options.url.replace(search, replace);
-          // Check to make sure existing dialogOptions aren't overridden.
-          if (!('dialogOptions' in instance.options.data)) {
-            instance.options.data.dialogOptions = {};
-          }
-          instance.options.data.dialogOptions.outsideInActiveEditableId = $(instance.element).parents('.outside-in-editable').attr('id');
-          instance.progress = {type: 'fullscreen'};
-        });
+
+      Drupal.ajax.instances.filter(function (instance) {
+        var hasElement = instance && !!instance.element;
+        var rendererOffCanvas = false;
+        var wrapperOffCanvas = false;
+        if (hasElement) {
+          rendererOffCanvas = $(instance.element).attr('data-dialog-renderer') === 'off_canvas';
+          wrapperOffCanvas = instance.options.url.indexOf('drupal_dialog_off_canvas') === -1;
+        }
+        return hasElement && rendererOffCanvas && wrapperOffCanvas;
+      }).forEach(function (instance) {
+        instance.options.url = instance.options.url.replace(search, replace);
+
+        if (!('dialogOptions' in instance.options.data)) {
+          instance.options.data.dialogOptions = {};
+        }
+        instance.options.data.dialogOptions.outsideInActiveEditableId = $(instance.element).parents('.outside-in-editable').attr('id');
+        instance.progress = { type: 'fullscreen' };
+      });
     }
   };
 
-  // Manage Active editable class on opening and closing of the dialog.
   $(window).on({
-    'dialog:beforecreate': function (event, dialog, $element, settings) {
+    'dialog:beforecreate': function dialogBeforecreate(event, dialog, $element, settings) {
       if ($element.is('#drupal-off-canvas')) {
         $('body .outside-in-active-editable').removeClass('outside-in-active-editable');
         var $activeElement = $('#' + settings.outsideInActiveEditableId);
@@ -255,11 +165,10 @@
         }
       }
     },
-    'dialog:beforeclose': function (event, dialog, $element) {
+    'dialog:beforeclose': function dialogBeforeclose(event, dialog, $element) {
       if ($element.is('#drupal-off-canvas')) {
         $('body .outside-in-active-editable').removeClass('outside-in-active-editable');
       }
     }
   });
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/modules/path/path.es6.js b/core/modules/path/path.es6.js
new file mode 100644
index 000000000000..dfa2c052a1af
--- /dev/null
+++ b/core/modules/path/path.es6.js
@@ -0,0 +1,29 @@
+/**
+ * @file
+ * Attaches behaviors for the Path module.
+ */
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * Behaviors for settings summaries on path edit forms.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches summary behavior on path edit forms.
+   */
+  Drupal.behaviors.pathDetailsSummaries = {
+    attach: function (context) {
+      $(context).find('.path-form').drupalSetSummary(function (context) {
+        var path = $('.js-form-item-path-0-alias input').val();
+
+        return path ?
+          Drupal.t('Alias: @alias', {'@alias': path}) :
+          Drupal.t('No alias');
+      });
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/core/modules/path/path.js b/core/modules/path/path.js
index dfa2c052a1af..46ce540916e8 100644
--- a/core/modules/path/path.js
+++ b/core/modules/path/path.js
@@ -1,29 +1,22 @@
 /**
- * @file
- * Attaches behaviors for the Path module.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/path/path.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
+
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   * Behaviors for settings summaries on path edit forms.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches summary behavior on path edit forms.
-   */
   Drupal.behaviors.pathDetailsSummaries = {
-    attach: function (context) {
+    attach: function attach(context) {
       $(context).find('.path-form').drupalSetSummary(function (context) {
         var path = $('.js-form-item-path-0-alias input').val();
 
-        return path ?
-          Drupal.t('Alias: @alias', {'@alias': path}) :
-          Drupal.t('No alias');
+        return path ? Drupal.t('Alias: @alias', { '@alias': path }) : Drupal.t('No alias');
       });
     }
   };
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/modules/quickedit/js/editors/formEditor.es6.js b/core/modules/quickedit/js/editors/formEditor.es6.js
new file mode 100644
index 000000000000..374e5c21eba4
--- /dev/null
+++ b/core/modules/quickedit/js/editors/formEditor.es6.js
@@ -0,0 +1,255 @@
+/**
+ * @file
+ * Form-based in-place editor. Works for any field type.
+ */
+
+(function ($, Drupal, _) {
+
+  'use strict';
+
+  /**
+   * @constructor
+   *
+   * @augments Drupal.quickedit.EditorView
+   */
+  Drupal.quickedit.editors.form = Drupal.quickedit.EditorView.extend(/** @lends Drupal.quickedit.editors.form# */{
+
+    /**
+     * Tracks form container DOM element that is used while in-place editing.
+     *
+     * @type {jQuery}
+     */
+    $formContainer: null,
+
+    /**
+     * Holds the {@link Drupal.Ajax} object.
+     *
+     * @type {Drupal.Ajax}
+     */
+    formSaveAjax: null,
+
+    /**
+     * @inheritdoc
+     *
+     * @param {object} fieldModel
+     *   The field model that holds the state.
+     * @param {string} state
+     *   The state to change to.
+     */
+    stateChange: function (fieldModel, state) {
+      var from = fieldModel.previous('state');
+      var to = state;
+      switch (to) {
+        case 'inactive':
+          break;
+
+        case 'candidate':
+          if (from !== 'inactive') {
+            this.removeForm();
+          }
+          break;
+
+        case 'highlighted':
+          break;
+
+        case 'activating':
+          // If coming from an invalid state, then the form is already loaded.
+          if (from !== 'invalid') {
+            this.loadForm();
+          }
+          break;
+
+        case 'active':
+          break;
+
+        case 'changed':
+          break;
+
+        case 'saving':
+          this.save();
+          break;
+
+        case 'saved':
+          break;
+
+        case 'invalid':
+          this.showValidationErrors();
+          break;
+      }
+    },
+
+    /**
+     * @inheritdoc
+     *
+     * @return {object}
+     *   A settings object for the quick edit UI.
+     */
+    getQuickEditUISettings: function () {
+      return {padding: true, unifiedToolbar: true, fullWidthToolbar: true, popup: true};
+    },
+
+    /**
+     * Loads the form for this field, displays it on top of the actual field.
+     */
+    loadForm: function () {
+      var fieldModel = this.fieldModel;
+
+      // Generate a DOM-compatible ID for the form container DOM element.
+      var id = 'quickedit-form-for-' + fieldModel.id.replace(/[\/\[\]]/g, '_');
+
+      // Render form container.
+      var $formContainer = this.$formContainer = $(Drupal.theme('quickeditFormContainer', {
+        id: id,
+        loadingMsg: Drupal.t('Loading…')
+      }));
+      $formContainer
+        .find('.quickedit-form')
+        .addClass('quickedit-editable quickedit-highlighted quickedit-editing')
+        .attr('role', 'dialog');
+
+      // Insert form container in DOM.
+      if (this.$el.css('display') === 'inline') {
+        $formContainer.prependTo(this.$el.offsetParent());
+        // Position the form container to render on top of the field's element.
+        var pos = this.$el.position();
+        $formContainer.css('left', pos.left).css('top', pos.top);
+      }
+      else {
+        $formContainer.insertBefore(this.$el);
+      }
+
+      // Load form, insert it into the form container and attach event handlers.
+      var formOptions = {
+        fieldID: fieldModel.get('fieldID'),
+        $el: this.$el,
+        nocssjs: false,
+        // Reset an existing entry for this entity in the PrivateTempStore (if
+        // any) when loading the field. Logically speaking, this should happen
+        // in a separate request because this is an entity-level operation, not
+        // a field-level operation. But that would require an additional
+        // request, that might not even be necessary: it is only when a user
+        // loads a first changed field for an entity that this needs to happen:
+        // precisely now!
+        reset: !fieldModel.get('entity').get('inTempStore')
+      };
+      Drupal.quickedit.util.form.load(formOptions, function (form, ajax) {
+        Drupal.AjaxCommands.prototype.insert(ajax, {
+          data: form,
+          selector: '#' + id + ' .placeholder'
+        });
+
+        $formContainer
+          .on('formUpdated.quickedit', ':input', function (event) {
+            var state = fieldModel.get('state');
+            // If the form is in an invalid state, it will persist on the page.
+            // Set the field to activating so that the user can correct the
+            // invalid value.
+            if (state === 'invalid') {
+              fieldModel.set('state', 'activating');
+            }
+            // Otherwise assume that the fieldModel is in a candidate state and
+            // set it to changed on formUpdate.
+            else {
+              fieldModel.set('state', 'changed');
+            }
+          })
+          .on('keypress.quickedit', 'input', function (event) {
+            if (event.keyCode === 13) {
+              return false;
+            }
+          });
+
+        // The in-place editor has loaded; change state to 'active'.
+        fieldModel.set('state', 'active');
+      });
+    },
+
+    /**
+     * Removes the form for this field, detaches behaviors and event handlers.
+     */
+    removeForm: function () {
+      if (this.$formContainer === null) {
+        return;
+      }
+
+      delete this.formSaveAjax;
+      // Allow form widgets to detach properly.
+      Drupal.detachBehaviors(this.$formContainer.get(0), null, 'unload');
+      this.$formContainer
+        .off('change.quickedit', ':input')
+        .off('keypress.quickedit', 'input')
+        .remove();
+      this.$formContainer = null;
+    },
+
+    /**
+     * @inheritdoc
+     */
+    save: function () {
+      var $formContainer = this.$formContainer;
+      var $submit = $formContainer.find('.quickedit-form-submit');
+      var editorModel = this.model;
+      var fieldModel = this.fieldModel;
+
+      function cleanUpAjax() {
+        Drupal.quickedit.util.form.unajaxifySaving(formSaveAjax);
+        formSaveAjax = null;
+      }
+
+      // Create an AJAX object for the form associated with the field.
+      var formSaveAjax = Drupal.quickedit.util.form.ajaxifySaving({
+        nocssjs: false,
+        other_view_modes: fieldModel.findOtherViewModes()
+      }, $submit);
+
+      // Successfully saved.
+      formSaveAjax.commands.quickeditFieldFormSaved = function (ajax, response, status) {
+        cleanUpAjax();
+        // First, transition the state to 'saved'.
+        fieldModel.set('state', 'saved');
+        // Second, set the 'htmlForOtherViewModes' attribute, so that when this
+        // field is rerendered, the change can be propagated to other instances
+        // of this field, which may be displayed in different view modes.
+        fieldModel.set('htmlForOtherViewModes', response.other_view_modes);
+        // Finally, set the 'html' attribute on the field model. This will cause
+        // the field to be rerendered.
+        _.defer(function () {
+          fieldModel.set('html', response.data);
+        });
+      };
+
+      // Unsuccessfully saved; validation errors.
+      formSaveAjax.commands.quickeditFieldFormValidationErrors = function (ajax, response, status) {
+        editorModel.set('validationErrors', response.data);
+        fieldModel.set('state', 'invalid');
+      };
+
+      // The quickeditFieldForm AJAX command is called upon attempting to save
+      // the form; Form API will mark which form items have errors, if any. This
+      // command is invoked only if validation errors exist and then it runs
+      // before editFieldFormValidationErrors().
+      formSaveAjax.commands.quickeditFieldForm = function (ajax, response, status) {
+        Drupal.AjaxCommands.prototype.insert(ajax, {
+          data: response.data,
+          selector: '#' + $formContainer.attr('id') + ' form'
+        });
+      };
+
+      // Click the form's submit button; the scoped AJAX commands above will
+      // handle the server's response.
+      $submit.trigger('click.quickedit');
+    },
+
+    /**
+     * @inheritdoc
+     */
+    showValidationErrors: function () {
+      this.$formContainer
+        .find('.quickedit-form')
+        .addClass('quickedit-validation-error')
+        .find('form')
+        .prepend(this.model.get('validationErrors'));
+    }
+  });
+
+})(jQuery, Drupal, _);
diff --git a/core/modules/quickedit/js/editors/formEditor.js b/core/modules/quickedit/js/editors/formEditor.js
index 374e5c21eba4..a60747c58357 100644
--- a/core/modules/quickedit/js/editors/formEditor.js
+++ b/core/modules/quickedit/js/editors/formEditor.js
@@ -1,42 +1,21 @@
 /**
- * @file
- * Form-based in-place editor. Works for any field type.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/quickedit/js/editors/formEditor.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, _) {
 
   'use strict';
 
-  /**
-   * @constructor
-   *
-   * @augments Drupal.quickedit.EditorView
-   */
-  Drupal.quickedit.editors.form = Drupal.quickedit.EditorView.extend(/** @lends Drupal.quickedit.editors.form# */{
-
-    /**
-     * Tracks form container DOM element that is used while in-place editing.
-     *
-     * @type {jQuery}
-     */
+  Drupal.quickedit.editors.form = Drupal.quickedit.EditorView.extend({
     $formContainer: null,
 
-    /**
-     * Holds the {@link Drupal.Ajax} object.
-     *
-     * @type {Drupal.Ajax}
-     */
     formSaveAjax: null,
 
-    /**
-     * @inheritdoc
-     *
-     * @param {object} fieldModel
-     *   The field model that holds the state.
-     * @param {string} state
-     *   The state to change to.
-     */
-    stateChange: function (fieldModel, state) {
+    stateChange: function stateChange(fieldModel, state) {
       var from = fieldModel.previous('state');
       var to = state;
       switch (to) {
@@ -53,7 +32,6 @@
           break;
 
         case 'activating':
-          // If coming from an invalid state, then the form is already loaded.
           if (from !== 'invalid') {
             this.loadForm();
           }
@@ -78,58 +56,35 @@
       }
     },
 
-    /**
-     * @inheritdoc
-     *
-     * @return {object}
-     *   A settings object for the quick edit UI.
-     */
-    getQuickEditUISettings: function () {
-      return {padding: true, unifiedToolbar: true, fullWidthToolbar: true, popup: true};
+    getQuickEditUISettings: function getQuickEditUISettings() {
+      return { padding: true, unifiedToolbar: true, fullWidthToolbar: true, popup: true };
     },
 
-    /**
-     * Loads the form for this field, displays it on top of the actual field.
-     */
-    loadForm: function () {
+    loadForm: function loadForm() {
       var fieldModel = this.fieldModel;
 
-      // Generate a DOM-compatible ID for the form container DOM element.
       var id = 'quickedit-form-for-' + fieldModel.id.replace(/[\/\[\]]/g, '_');
 
-      // Render form container.
       var $formContainer = this.$formContainer = $(Drupal.theme('quickeditFormContainer', {
         id: id,
         loadingMsg: Drupal.t('Loading…')
       }));
-      $formContainer
-        .find('.quickedit-form')
-        .addClass('quickedit-editable quickedit-highlighted quickedit-editing')
-        .attr('role', 'dialog');
+      $formContainer.find('.quickedit-form').addClass('quickedit-editable quickedit-highlighted quickedit-editing').attr('role', 'dialog');
 
-      // Insert form container in DOM.
       if (this.$el.css('display') === 'inline') {
         $formContainer.prependTo(this.$el.offsetParent());
-        // Position the form container to render on top of the field's element.
+
         var pos = this.$el.position();
         $formContainer.css('left', pos.left).css('top', pos.top);
-      }
-      else {
+      } else {
         $formContainer.insertBefore(this.$el);
       }
 
-      // Load form, insert it into the form container and attach event handlers.
       var formOptions = {
         fieldID: fieldModel.get('fieldID'),
         $el: this.$el,
         nocssjs: false,
-        // Reset an existing entry for this entity in the PrivateTempStore (if
-        // any) when loading the field. Logically speaking, this should happen
-        // in a separate request because this is an entity-level operation, not
-        // a field-level operation. But that would require an additional
-        // request, that might not even be necessary: it is only when a user
-        // loads a first changed field for an entity that this needs to happen:
-        // precisely now!
+
         reset: !fieldModel.get('entity').get('inTempStore')
       };
       Drupal.quickedit.util.form.load(formOptions, function (form, ajax) {
@@ -138,54 +93,37 @@
           selector: '#' + id + ' .placeholder'
         });
 
-        $formContainer
-          .on('formUpdated.quickedit', ':input', function (event) {
-            var state = fieldModel.get('state');
-            // If the form is in an invalid state, it will persist on the page.
-            // Set the field to activating so that the user can correct the
-            // invalid value.
-            if (state === 'invalid') {
-              fieldModel.set('state', 'activating');
-            }
-            // Otherwise assume that the fieldModel is in a candidate state and
-            // set it to changed on formUpdate.
-            else {
+        $formContainer.on('formUpdated.quickedit', ':input', function (event) {
+          var state = fieldModel.get('state');
+
+          if (state === 'invalid') {
+            fieldModel.set('state', 'activating');
+          } else {
               fieldModel.set('state', 'changed');
             }
-          })
-          .on('keypress.quickedit', 'input', function (event) {
-            if (event.keyCode === 13) {
-              return false;
-            }
-          });
+        }).on('keypress.quickedit', 'input', function (event) {
+          if (event.keyCode === 13) {
+            return false;
+          }
+        });
 
-        // The in-place editor has loaded; change state to 'active'.
         fieldModel.set('state', 'active');
       });
     },
 
-    /**
-     * Removes the form for this field, detaches behaviors and event handlers.
-     */
-    removeForm: function () {
+    removeForm: function removeForm() {
       if (this.$formContainer === null) {
         return;
       }
 
       delete this.formSaveAjax;
-      // Allow form widgets to detach properly.
+
       Drupal.detachBehaviors(this.$formContainer.get(0), null, 'unload');
-      this.$formContainer
-        .off('change.quickedit', ':input')
-        .off('keypress.quickedit', 'input')
-        .remove();
+      this.$formContainer.off('change.quickedit', ':input').off('keypress.quickedit', 'input').remove();
       this.$formContainer = null;
     },
 
-    /**
-     * @inheritdoc
-     */
-    save: function () {
+    save: function save() {
       var $formContainer = this.$formContainer;
       var $submit = $formContainer.find('.quickedit-form-submit');
       var editorModel = this.model;
@@ -196,38 +134,28 @@
         formSaveAjax = null;
       }
 
-      // Create an AJAX object for the form associated with the field.
       var formSaveAjax = Drupal.quickedit.util.form.ajaxifySaving({
         nocssjs: false,
         other_view_modes: fieldModel.findOtherViewModes()
       }, $submit);
 
-      // Successfully saved.
       formSaveAjax.commands.quickeditFieldFormSaved = function (ajax, response, status) {
         cleanUpAjax();
-        // First, transition the state to 'saved'.
+
         fieldModel.set('state', 'saved');
-        // Second, set the 'htmlForOtherViewModes' attribute, so that when this
-        // field is rerendered, the change can be propagated to other instances
-        // of this field, which may be displayed in different view modes.
+
         fieldModel.set('htmlForOtherViewModes', response.other_view_modes);
-        // Finally, set the 'html' attribute on the field model. This will cause
-        // the field to be rerendered.
+
         _.defer(function () {
           fieldModel.set('html', response.data);
         });
       };
 
-      // Unsuccessfully saved; validation errors.
       formSaveAjax.commands.quickeditFieldFormValidationErrors = function (ajax, response, status) {
         editorModel.set('validationErrors', response.data);
         fieldModel.set('state', 'invalid');
       };
 
-      // The quickeditFieldForm AJAX command is called upon attempting to save
-      // the form; Form API will mark which form items have errors, if any. This
-      // command is invoked only if validation errors exist and then it runs
-      // before editFieldFormValidationErrors().
       formSaveAjax.commands.quickeditFieldForm = function (ajax, response, status) {
         Drupal.AjaxCommands.prototype.insert(ajax, {
           data: response.data,
@@ -235,21 +163,11 @@
         });
       };
 
-      // Click the form's submit button; the scoped AJAX commands above will
-      // handle the server's response.
       $submit.trigger('click.quickedit');
     },
 
-    /**
-     * @inheritdoc
-     */
-    showValidationErrors: function () {
-      this.$formContainer
-        .find('.quickedit-form')
-        .addClass('quickedit-validation-error')
-        .find('form')
-        .prepend(this.model.get('validationErrors'));
+    showValidationErrors: function showValidationErrors() {
+      this.$formContainer.find('.quickedit-form').addClass('quickedit-validation-error').find('form').prepend(this.model.get('validationErrors'));
     }
   });
-
-})(jQuery, Drupal, _);
+})(jQuery, Drupal, _);
\ No newline at end of file
diff --git a/core/modules/quickedit/js/editors/plainTextEditor.es6.js b/core/modules/quickedit/js/editors/plainTextEditor.es6.js
new file mode 100644
index 000000000000..71f9b74e58bf
--- /dev/null
+++ b/core/modules/quickedit/js/editors/plainTextEditor.es6.js
@@ -0,0 +1,144 @@
+/**
+ * @file
+ * ContentEditable-based in-place editor for plain text content.
+ */
+
+(function ($, _, Drupal) {
+
+  'use strict';
+
+  Drupal.quickedit.editors.plain_text = Drupal.quickedit.EditorView.extend(/** @lends Drupal.quickedit.editors.plain_text# */{
+
+    /**
+     * Stores the textual DOM element that is being in-place edited.
+     */
+    $textElement: null,
+
+    /**
+     * @constructs
+     *
+     * @augments Drupal.quickedit.EditorView
+     *
+     * @param {object} options
+     *   Options for the plain text editor.
+     */
+    initialize: function (options) {
+      Drupal.quickedit.EditorView.prototype.initialize.call(this, options);
+
+      var editorModel = this.model;
+      var fieldModel = this.fieldModel;
+
+      // Store the original value of this field. Necessary for reverting
+      // changes.
+      var $textElement;
+      var $fieldItems = this.$el.find('.quickedit-field');
+      if ($fieldItems.length) {
+        $textElement = this.$textElement = $fieldItems.eq(0);
+      }
+      else {
+        $textElement = this.$textElement = this.$el;
+      }
+      editorModel.set('originalValue', $.trim(this.$textElement.text()));
+
+      // Sets the state to 'changed' whenever the value changes.
+      var previousText = editorModel.get('originalValue');
+      $textElement.on('keyup paste', function (event) {
+        var currentText = $.trim($textElement.text());
+        if (previousText !== currentText) {
+          previousText = currentText;
+          editorModel.set('currentValue', currentText);
+          fieldModel.set('state', 'changed');
+        }
+      });
+    },
+
+    /**
+     * @inheritdoc
+     *
+     * @return {jQuery}
+     *   The text element for the plain text editor.
+     */
+    getEditedElement: function () {
+      return this.$textElement;
+    },
+
+    /**
+     * @inheritdoc
+     *
+     * @param {object} fieldModel
+     *   The field model that holds the state.
+     * @param {string} state
+     *   The state to change to.
+     * @param {object} options
+     *   State options, if needed by the state change.
+     */
+    stateChange: function (fieldModel, state, options) {
+      var from = fieldModel.previous('state');
+      var to = state;
+      switch (to) {
+        case 'inactive':
+          break;
+
+        case 'candidate':
+          if (from !== 'inactive') {
+            this.$textElement.removeAttr('contenteditable');
+          }
+          if (from === 'invalid') {
+            this.removeValidationErrors();
+          }
+          break;
+
+        case 'highlighted':
+          break;
+
+        case 'activating':
+          // Defer updating the field model until the current state change has
+          // propagated, to not trigger a nested state change event.
+          _.defer(function () {
+            fieldModel.set('state', 'active');
+          });
+          break;
+
+        case 'active':
+          this.$textElement.attr('contenteditable', 'true');
+          break;
+
+        case 'changed':
+          break;
+
+        case 'saving':
+          if (from === 'invalid') {
+            this.removeValidationErrors();
+          }
+          this.save(options);
+          break;
+
+        case 'saved':
+          break;
+
+        case 'invalid':
+          this.showValidationErrors();
+          break;
+      }
+    },
+
+    /**
+     * @inheritdoc
+     *
+     * @return {object}
+     *   A settings object for the quick edit UI.
+     */
+    getQuickEditUISettings: function () {
+      return {padding: true, unifiedToolbar: false, fullWidthToolbar: false, popup: false};
+    },
+
+    /**
+     * @inheritdoc
+     */
+    revert: function () {
+      this.$textElement.html(this.model.get('originalValue'));
+    }
+
+  });
+
+})(jQuery, _, Drupal);
diff --git a/core/modules/quickedit/js/editors/plainTextEditor.js b/core/modules/quickedit/js/editors/plainTextEditor.js
index 71f9b74e58bf..7358c2692dc4 100644
--- a/core/modules/quickedit/js/editors/plainTextEditor.js
+++ b/core/modules/quickedit/js/editors/plainTextEditor.js
@@ -1,46 +1,33 @@
 /**
- * @file
- * ContentEditable-based in-place editor for plain text content.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/quickedit/js/editors/plainTextEditor.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, _, Drupal) {
 
   'use strict';
 
-  Drupal.quickedit.editors.plain_text = Drupal.quickedit.EditorView.extend(/** @lends Drupal.quickedit.editors.plain_text# */{
-
-    /**
-     * Stores the textual DOM element that is being in-place edited.
-     */
+  Drupal.quickedit.editors.plain_text = Drupal.quickedit.EditorView.extend({
     $textElement: null,
 
-    /**
-     * @constructs
-     *
-     * @augments Drupal.quickedit.EditorView
-     *
-     * @param {object} options
-     *   Options for the plain text editor.
-     */
-    initialize: function (options) {
+    initialize: function initialize(options) {
       Drupal.quickedit.EditorView.prototype.initialize.call(this, options);
 
       var editorModel = this.model;
       var fieldModel = this.fieldModel;
 
-      // Store the original value of this field. Necessary for reverting
-      // changes.
       var $textElement;
       var $fieldItems = this.$el.find('.quickedit-field');
       if ($fieldItems.length) {
         $textElement = this.$textElement = $fieldItems.eq(0);
-      }
-      else {
+      } else {
         $textElement = this.$textElement = this.$el;
       }
       editorModel.set('originalValue', $.trim(this.$textElement.text()));
 
-      // Sets the state to 'changed' whenever the value changes.
       var previousText = editorModel.get('originalValue');
       $textElement.on('keyup paste', function (event) {
         var currentText = $.trim($textElement.text());
@@ -52,27 +39,11 @@
       });
     },
 
-    /**
-     * @inheritdoc
-     *
-     * @return {jQuery}
-     *   The text element for the plain text editor.
-     */
-    getEditedElement: function () {
+    getEditedElement: function getEditedElement() {
       return this.$textElement;
     },
 
-    /**
-     * @inheritdoc
-     *
-     * @param {object} fieldModel
-     *   The field model that holds the state.
-     * @param {string} state
-     *   The state to change to.
-     * @param {object} options
-     *   State options, if needed by the state change.
-     */
-    stateChange: function (fieldModel, state, options) {
+    stateChange: function stateChange(fieldModel, state, options) {
       var from = fieldModel.previous('state');
       var to = state;
       switch (to) {
@@ -92,8 +63,6 @@
           break;
 
         case 'activating':
-          // Defer updating the field model until the current state change has
-          // propagated, to not trigger a nested state change event.
           _.defer(function () {
             fieldModel.set('state', 'active');
           });
@@ -122,23 +91,13 @@
       }
     },
 
-    /**
-     * @inheritdoc
-     *
-     * @return {object}
-     *   A settings object for the quick edit UI.
-     */
-    getQuickEditUISettings: function () {
-      return {padding: true, unifiedToolbar: false, fullWidthToolbar: false, popup: false};
+    getQuickEditUISettings: function getQuickEditUISettings() {
+      return { padding: true, unifiedToolbar: false, fullWidthToolbar: false, popup: false };
     },
 
-    /**
-     * @inheritdoc
-     */
-    revert: function () {
+    revert: function revert() {
       this.$textElement.html(this.model.get('originalValue'));
     }
 
   });
-
-})(jQuery, _, Drupal);
+})(jQuery, _, Drupal);
\ No newline at end of file
diff --git a/core/modules/quickedit/js/models/AppModel.es6.js b/core/modules/quickedit/js/models/AppModel.es6.js
new file mode 100644
index 000000000000..6c473ac97497
--- /dev/null
+++ b/core/modules/quickedit/js/models/AppModel.es6.js
@@ -0,0 +1,57 @@
+/**
+ * @file
+ * A Backbone Model for the state of the in-place editing application.
+ *
+ * @see Drupal.quickedit.AppView
+ */
+
+(function (Backbone, Drupal) {
+
+  'use strict';
+
+  /**
+   * @constructor
+   *
+   * @augments Backbone.Model
+   */
+  Drupal.quickedit.AppModel = Backbone.Model.extend(/** @lends Drupal.quickedit.AppModel# */{
+
+    /**
+     * @type {object}
+     *
+     * @prop {Drupal.quickedit.FieldModel} highlightedField
+     * @prop {Drupal.quickedit.FieldModel} activeField
+     * @prop {Drupal.dialog~dialogDefinition} activeModal
+     */
+    defaults: /** @lends Drupal.quickedit.AppModel# */{
+
+      /**
+       * The currently state='highlighted' Drupal.quickedit.FieldModel, if any.
+       *
+       * @type {Drupal.quickedit.FieldModel}
+       *
+       * @see Drupal.quickedit.FieldModel.states
+       */
+      highlightedField: null,
+
+      /**
+       * The currently state = 'active' Drupal.quickedit.FieldModel, if any.
+       *
+       * @type {Drupal.quickedit.FieldModel}
+       *
+       * @see Drupal.quickedit.FieldModel.states
+       */
+      activeField: null,
+
+      /**
+       * Reference to a {@link Drupal.dialog} instance if a state change
+       * requires confirmation.
+       *
+       * @type {Drupal.dialog~dialogDefinition}
+       */
+      activeModal: null
+    }
+
+  });
+
+}(Backbone, Drupal));
diff --git a/core/modules/quickedit/js/models/AppModel.js b/core/modules/quickedit/js/models/AppModel.js
index 6c473ac97497..7abc0d9b0ad8 100644
--- a/core/modules/quickedit/js/models/AppModel.js
+++ b/core/modules/quickedit/js/models/AppModel.js
@@ -1,57 +1,23 @@
 /**
- * @file
- * A Backbone Model for the state of the in-place editing application.
- *
- * @see Drupal.quickedit.AppView
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/quickedit/js/models/AppModel.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function (Backbone, Drupal) {
 
   'use strict';
 
-  /**
-   * @constructor
-   *
-   * @augments Backbone.Model
-   */
-  Drupal.quickedit.AppModel = Backbone.Model.extend(/** @lends Drupal.quickedit.AppModel# */{
-
-    /**
-     * @type {object}
-     *
-     * @prop {Drupal.quickedit.FieldModel} highlightedField
-     * @prop {Drupal.quickedit.FieldModel} activeField
-     * @prop {Drupal.dialog~dialogDefinition} activeModal
-     */
-    defaults: /** @lends Drupal.quickedit.AppModel# */{
-
-      /**
-       * The currently state='highlighted' Drupal.quickedit.FieldModel, if any.
-       *
-       * @type {Drupal.quickedit.FieldModel}
-       *
-       * @see Drupal.quickedit.FieldModel.states
-       */
+  Drupal.quickedit.AppModel = Backbone.Model.extend({
+    defaults: {
       highlightedField: null,
 
-      /**
-       * The currently state = 'active' Drupal.quickedit.FieldModel, if any.
-       *
-       * @type {Drupal.quickedit.FieldModel}
-       *
-       * @see Drupal.quickedit.FieldModel.states
-       */
       activeField: null,
 
-      /**
-       * Reference to a {@link Drupal.dialog} instance if a state change
-       * requires confirmation.
-       *
-       * @type {Drupal.dialog~dialogDefinition}
-       */
       activeModal: null
     }
 
   });
-
-}(Backbone, Drupal));
+})(Backbone, Drupal);
\ No newline at end of file
diff --git a/core/modules/quickedit/js/models/BaseModel.es6.js b/core/modules/quickedit/js/models/BaseModel.es6.js
new file mode 100644
index 000000000000..7579b6f03b0d
--- /dev/null
+++ b/core/modules/quickedit/js/models/BaseModel.es6.js
@@ -0,0 +1,60 @@
+/**
+ * @file
+ * A Backbone Model subclass that enforces validation when calling set().
+ */
+
+(function (Drupal, Backbone) {
+
+  'use strict';
+
+  Drupal.quickedit.BaseModel = Backbone.Model.extend(/** @lends Drupal.quickedit.BaseModel# */{
+
+    /**
+     * @constructs
+     *
+     * @augments Backbone.Model
+     *
+     * @param {object} options
+     *   Options for the base model-
+     *
+     * @return {Drupal.quickedit.BaseModel}
+     *   A quickedit base model.
+     */
+    initialize: function (options) {
+      this.__initialized = true;
+      return Backbone.Model.prototype.initialize.call(this, options);
+    },
+
+    /**
+     * Set a value on the model
+     *
+     * @param {object|string} key
+     *   The key to set a value for.
+     * @param {*} val
+     *   The value to set.
+     * @param {object} [options]
+     *   Options for the model.
+     *
+     * @return {*}
+     *   The result of `Backbone.Model.prototype.set` with the specified
+     *   parameters.
+     */
+    set: function (key, val, options) {
+      if (this.__initialized) {
+        // Deal with both the "key", value and {key:value}-style arguments.
+        if (typeof key === 'object') {
+          key.validate = true;
+        }
+        else {
+          if (!options) {
+            options = {};
+          }
+          options.validate = true;
+        }
+      }
+      return Backbone.Model.prototype.set.call(this, key, val, options);
+    }
+
+  });
+
+}(Drupal, Backbone));
diff --git a/core/modules/quickedit/js/models/BaseModel.js b/core/modules/quickedit/js/models/BaseModel.js
index 7579b6f03b0d..6b882bbe374c 100644
--- a/core/modules/quickedit/js/models/BaseModel.js
+++ b/core/modules/quickedit/js/models/BaseModel.js
@@ -1,51 +1,27 @@
 /**
- * @file
- * A Backbone Model subclass that enforces validation when calling set().
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/quickedit/js/models/BaseModel.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
+var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
 
 (function (Drupal, Backbone) {
 
   'use strict';
 
-  Drupal.quickedit.BaseModel = Backbone.Model.extend(/** @lends Drupal.quickedit.BaseModel# */{
-
-    /**
-     * @constructs
-     *
-     * @augments Backbone.Model
-     *
-     * @param {object} options
-     *   Options for the base model-
-     *
-     * @return {Drupal.quickedit.BaseModel}
-     *   A quickedit base model.
-     */
-    initialize: function (options) {
+  Drupal.quickedit.BaseModel = Backbone.Model.extend({
+    initialize: function initialize(options) {
       this.__initialized = true;
       return Backbone.Model.prototype.initialize.call(this, options);
     },
 
-    /**
-     * Set a value on the model
-     *
-     * @param {object|string} key
-     *   The key to set a value for.
-     * @param {*} val
-     *   The value to set.
-     * @param {object} [options]
-     *   Options for the model.
-     *
-     * @return {*}
-     *   The result of `Backbone.Model.prototype.set` with the specified
-     *   parameters.
-     */
-    set: function (key, val, options) {
+    set: function set(key, val, options) {
       if (this.__initialized) {
-        // Deal with both the "key", value and {key:value}-style arguments.
-        if (typeof key === 'object') {
+        if ((typeof key === 'undefined' ? 'undefined' : _typeof(key)) === 'object') {
           key.validate = true;
-        }
-        else {
+        } else {
           if (!options) {
             options = {};
           }
@@ -56,5 +32,4 @@
     }
 
   });
-
-}(Drupal, Backbone));
+})(Drupal, Backbone);
\ No newline at end of file
diff --git a/core/modules/quickedit/js/models/EditorModel.es6.js b/core/modules/quickedit/js/models/EditorModel.es6.js
new file mode 100644
index 000000000000..4b6177bc43fd
--- /dev/null
+++ b/core/modules/quickedit/js/models/EditorModel.es6.js
@@ -0,0 +1,54 @@
+/**
+ * @file
+ * A Backbone Model for the state of an in-place editor.
+ *
+ * @see Drupal.quickedit.EditorView
+ */
+
+(function (Backbone, Drupal) {
+
+  'use strict';
+
+  /**
+   * @constructor
+   *
+   * @augments Backbone.Model
+   */
+  Drupal.quickedit.EditorModel = Backbone.Model.extend(/** @lends Drupal.quickedit.EditorModel# */{
+
+    /**
+     * @type {object}
+     *
+     * @prop {string} originalValue
+     * @prop {string} currentValue
+     * @prop {Array} validationErrors
+     */
+    defaults: /** @lends Drupal.quickedit.EditorModel# */{
+
+      /**
+       * Not the full HTML representation of this field, but the "actual"
+       * original value of the field, stored by the used in-place editor, and
+       * in a representation that can be chosen by the in-place editor.
+       *
+       * @type {string}
+       */
+      originalValue: null,
+
+      /**
+       * Analogous to originalValue, but the current value.
+       *
+       * @type {string}
+       */
+      currentValue: null,
+
+      /**
+       * Stores any validation errors to be rendered.
+       *
+       * @type {Array}
+       */
+      validationErrors: null
+    }
+
+  });
+
+}(Backbone, Drupal));
diff --git a/core/modules/quickedit/js/models/EditorModel.js b/core/modules/quickedit/js/models/EditorModel.js
index 4b6177bc43fd..29a0c93a9f50 100644
--- a/core/modules/quickedit/js/models/EditorModel.js
+++ b/core/modules/quickedit/js/models/EditorModel.js
@@ -1,54 +1,23 @@
 /**
- * @file
- * A Backbone Model for the state of an in-place editor.
- *
- * @see Drupal.quickedit.EditorView
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/quickedit/js/models/EditorModel.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function (Backbone, Drupal) {
 
   'use strict';
 
-  /**
-   * @constructor
-   *
-   * @augments Backbone.Model
-   */
-  Drupal.quickedit.EditorModel = Backbone.Model.extend(/** @lends Drupal.quickedit.EditorModel# */{
-
-    /**
-     * @type {object}
-     *
-     * @prop {string} originalValue
-     * @prop {string} currentValue
-     * @prop {Array} validationErrors
-     */
-    defaults: /** @lends Drupal.quickedit.EditorModel# */{
-
-      /**
-       * Not the full HTML representation of this field, but the "actual"
-       * original value of the field, stored by the used in-place editor, and
-       * in a representation that can be chosen by the in-place editor.
-       *
-       * @type {string}
-       */
+  Drupal.quickedit.EditorModel = Backbone.Model.extend({
+    defaults: {
       originalValue: null,
 
-      /**
-       * Analogous to originalValue, but the current value.
-       *
-       * @type {string}
-       */
       currentValue: null,
 
-      /**
-       * Stores any validation errors to be rendered.
-       *
-       * @type {Array}
-       */
       validationErrors: null
     }
 
   });
-
-}(Backbone, Drupal));
+})(Backbone, Drupal);
\ No newline at end of file
diff --git a/core/modules/quickedit/js/models/EntityModel.es6.js b/core/modules/quickedit/js/models/EntityModel.es6.js
new file mode 100644
index 000000000000..f444fbb91768
--- /dev/null
+++ b/core/modules/quickedit/js/models/EntityModel.es6.js
@@ -0,0 +1,741 @@
+/**
+ * @file
+ * A Backbone Model for the state of an in-place editable entity in the DOM.
+ */
+
+(function (_, $, Backbone, Drupal) {
+
+  'use strict';
+
+  Drupal.quickedit.EntityModel = Drupal.quickedit.BaseModel.extend(/** @lends Drupal.quickedit.EntityModel# */{
+
+    /**
+     * @type {object}
+     */
+    defaults: /** @lends Drupal.quickedit.EntityModel# */{
+
+      /**
+       * The DOM element that represents this entity.
+       *
+       * It may seem bizarre to have a DOM element in a Backbone Model, but we
+       * need to be able to map entities in the DOM to EntityModels in memory.
+       *
+       * @type {HTMLElement}
+       */
+      el: null,
+
+      /**
+       * An entity ID, of the form `<entity type>/<entity ID>`
+       *
+       * @example
+       * "node/1"
+       *
+       * @type {string}
+       */
+      entityID: null,
+
+      /**
+       * An entity instance ID.
+       *
+       * The first instance of a specific entity (i.e. with a given entity ID)
+       * is assigned 0, the second 1, and so on.
+       *
+       * @type {number}
+       */
+      entityInstanceID: null,
+
+      /**
+       * The unique ID of this entity instance on the page, of the form
+       * `<entity type>/<entity ID>[entity instance ID]`
+       *
+       * @example
+       * "node/1[0]"
+       *
+       * @type {string}
+       */
+      id: null,
+
+      /**
+       * The label of the entity.
+       *
+       * @type {string}
+       */
+      label: null,
+
+      /**
+       * A FieldCollection for all fields of the entity.
+       *
+       * @type {Drupal.quickedit.FieldCollection}
+       *
+       * @see Drupal.quickedit.FieldCollection
+       */
+      fields: null,
+
+      // The attributes below are stateful. The ones above will never change
+      // during the life of a EntityModel instance.
+
+      /**
+       * Indicates whether this entity is currently being edited in-place.
+       *
+       * @type {bool}
+       */
+      isActive: false,
+
+      /**
+       * Whether one or more fields are already been stored in PrivateTempStore.
+       *
+       * @type {bool}
+       */
+      inTempStore: false,
+
+      /**
+       * Indicates whether a "Save" button is necessary or not.
+       *
+       * Whether one or more fields have already been stored in PrivateTempStore
+       * *or* the field that's currently being edited is in the 'changed' or a
+       * later state.
+       *
+       * @type {bool}
+       */
+      isDirty: false,
+
+      /**
+       * Whether the request to the server has been made to commit this entity.
+       *
+       * Used to prevent multiple such requests.
+       *
+       * @type {bool}
+       */
+      isCommitting: false,
+
+      /**
+       * The current processing state of an entity.
+       *
+       * @type {string}
+       */
+      state: 'closed',
+
+      /**
+       * IDs of fields whose new values have been stored in PrivateTempStore.
+       *
+       * We must store this on the EntityModel as well (even though it already
+       * is on the FieldModel) because when a field is rerendered, its
+       * FieldModel is destroyed and this allows us to transition it back to
+       * the proper state.
+       *
+       * @type {Array.<string>}
+       */
+      fieldsInTempStore: [],
+
+      /**
+       * A flag the tells the application that this EntityModel must be reloaded
+       * in order to restore the original values to its fields in the client.
+       *
+       * @type {bool}
+       */
+      reload: false
+    },
+
+    /**
+     * @constructs
+     *
+     * @augments Drupal.quickedit.BaseModel
+     */
+    initialize: function () {
+      this.set('fields', new Drupal.quickedit.FieldCollection());
+
+      // Respond to entity state changes.
+      this.listenTo(this, 'change:state', this.stateChange);
+
+      // The state of the entity is largely dependent on the state of its
+      // fields.
+      this.listenTo(this.get('fields'), 'change:state', this.fieldStateChange);
+
+      // Call Drupal.quickedit.BaseModel's initialize() method.
+      Drupal.quickedit.BaseModel.prototype.initialize.call(this);
+    },
+
+    /**
+     * Updates FieldModels' states when an EntityModel change occurs.
+     *
+     * @param {Drupal.quickedit.EntityModel} entityModel
+     *   The entity model
+     * @param {string} state
+     *   The state of the associated entity. One of
+     *   {@link Drupal.quickedit.EntityModel.states}.
+     * @param {object} options
+     *   Options for the entity model.
+     */
+    stateChange: function (entityModel, state, options) {
+      var to = state;
+      switch (to) {
+        case 'closed':
+          this.set({
+            isActive: false,
+            inTempStore: false,
+            isDirty: false
+          });
+          break;
+
+        case 'launching':
+          break;
+
+        case 'opening':
+          // Set the fields to candidate state.
+          entityModel.get('fields').each(function (fieldModel) {
+            fieldModel.set('state', 'candidate', options);
+          });
+          break;
+
+        case 'opened':
+          // The entity is now ready for editing!
+          this.set('isActive', true);
+          break;
+
+        case 'committing':
+          // The user indicated they want to save the entity.
+          var fields = this.get('fields');
+          // For fields that are in an active state, transition them to
+          // candidate.
+          fields.chain()
+            .filter(function (fieldModel) {
+              return _.intersection([fieldModel.get('state')], ['active']).length;
+            })
+            .each(function (fieldModel) {
+              fieldModel.set('state', 'candidate');
+            });
+          // For fields that are in a changed state, field values must first be
+          // stored in PrivateTempStore.
+          fields.chain()
+            .filter(function (fieldModel) {
+              return _.intersection([fieldModel.get('state')], Drupal.quickedit.app.changedFieldStates).length;
+            })
+            .each(function (fieldModel) {
+              fieldModel.set('state', 'saving');
+            });
+          break;
+
+        case 'deactivating':
+          var changedFields = this.get('fields')
+            .filter(function (fieldModel) {
+              return _.intersection([fieldModel.get('state')], ['changed', 'invalid']).length;
+            });
+          // If the entity contains unconfirmed or unsaved changes, return the
+          // entity to an opened state and ask the user if they would like to
+          // save the changes or discard the changes.
+          //   1. One of the fields is in a changed state. The changed field
+          //   might just be a change in the client or it might have been saved
+          //   to tempstore.
+          //   2. The saved flag is empty and the confirmed flag is empty. If
+          //   the entity has been saved to the server, the fields changed in
+          //   the client are irrelevant. If the changes are confirmed, then
+          //   proceed to set the fields to candidate state.
+          if ((changedFields.length || this.get('fieldsInTempStore').length) && (!options.saved && !options.confirmed)) {
+            // Cancel deactivation until the user confirms save or discard.
+            this.set('state', 'opened', {confirming: true});
+            // An action in reaction to state change must be deferred.
+            _.defer(function () {
+              Drupal.quickedit.app.confirmEntityDeactivation(entityModel);
+            });
+          }
+          else {
+            var invalidFields = this.get('fields')
+              .filter(function (fieldModel) {
+                return _.intersection([fieldModel.get('state')], ['invalid']).length;
+              });
+            // Indicate if this EntityModel needs to be reloaded in order to
+            // restore the original values of its fields.
+            entityModel.set('reload', (this.get('fieldsInTempStore').length || invalidFields.length));
+            // Set all fields to the 'candidate' state. A changed field may have
+            // to go through confirmation first.
+            entityModel.get('fields').each(function (fieldModel) {
+              // If the field is already in the candidate state, trigger a
+              // change event so that the entityModel can move to the next state
+              // in deactivation.
+              if (_.intersection([fieldModel.get('state')], ['candidate', 'highlighted']).length) {
+                fieldModel.trigger('change:state', fieldModel, fieldModel.get('state'), options);
+              }
+              else {
+                fieldModel.set('state', 'candidate', options);
+              }
+            });
+          }
+          break;
+
+        case 'closing':
+          // Set all fields to the 'inactive' state.
+          options.reason = 'stop';
+          this.get('fields').each(function (fieldModel) {
+            fieldModel.set({
+              inTempStore: false,
+              state: 'inactive'
+            }, options);
+          });
+          break;
+      }
+    },
+
+    /**
+     * Updates a Field and Entity model's "inTempStore" when appropriate.
+     *
+     * Helper function.
+     *
+     * @param {Drupal.quickedit.EntityModel} entityModel
+     *   The model of the entity for which a field's state attribute has
+     *   changed.
+     * @param {Drupal.quickedit.FieldModel} fieldModel
+     *   The model of the field whose state attribute has changed.
+     *
+     * @see Drupal.quickedit.EntityModel#fieldStateChange
+     */
+    _updateInTempStoreAttributes: function (entityModel, fieldModel) {
+      var current = fieldModel.get('state');
+      var previous = fieldModel.previous('state');
+      var fieldsInTempStore = entityModel.get('fieldsInTempStore');
+      // If the fieldModel changed to the 'saved' state: remember that this
+      // field was saved to PrivateTempStore.
+      if (current === 'saved') {
+        // Mark the entity as saved in PrivateTempStore, so that we can pass the
+        // proper "reset PrivateTempStore" boolean value when communicating with
+        // the server.
+        entityModel.set('inTempStore', true);
+        // Mark the field as saved in PrivateTempStore, so that visual
+        // indicators signifying just that may be rendered.
+        fieldModel.set('inTempStore', true);
+        // Remember that this field is in PrivateTempStore, restore when
+        // rerendered.
+        fieldsInTempStore.push(fieldModel.get('fieldID'));
+        fieldsInTempStore = _.uniq(fieldsInTempStore);
+        entityModel.set('fieldsInTempStore', fieldsInTempStore);
+      }
+      // If the fieldModel changed to the 'candidate' state from the
+      // 'inactive' state, then this is a field for this entity that got
+      // rerendered. Restore its previous 'inTempStore' attribute value.
+      else if (current === 'candidate' && previous === 'inactive') {
+        fieldModel.set('inTempStore', _.intersection([fieldModel.get('fieldID')], fieldsInTempStore).length > 0);
+      }
+    },
+
+    /**
+     * Reacts to state changes in this entity's fields.
+     *
+     * @param {Drupal.quickedit.FieldModel} fieldModel
+     *   The model of the field whose state attribute changed.
+     * @param {string} state
+     *   The state of the associated field. One of
+     *   {@link Drupal.quickedit.FieldModel.states}.
+     */
+    fieldStateChange: function (fieldModel, state) {
+      var entityModel = this;
+      var fieldState = state;
+      // Switch on the entityModel state.
+      // The EntityModel responds to FieldModel state changes as a function of
+      // its state. For example, a field switching back to 'candidate' state
+      // when its entity is in the 'opened' state has no effect on the entity.
+      // But that same switch back to 'candidate' state of a field when the
+      // entity is in the 'committing' state might allow the entity to proceed
+      // with the commit flow.
+      switch (this.get('state')) {
+        case 'closed':
+        case 'launching':
+          // It should be impossible to reach these: fields can't change state
+          // while the entity is closed or still launching.
+          break;
+
+        case 'opening':
+          // We must change the entity to the 'opened' state, but it must first
+          // be confirmed that all of its fieldModels have transitioned to the
+          // 'candidate' state.
+          // We do this here, because this is called every time a fieldModel
+          // changes state, hence each time this is called, we get closer to the
+          // goal of having all fieldModels in the 'candidate' state.
+          // A state change in reaction to another state change must be
+          // deferred.
+          _.defer(function () {
+            entityModel.set('state', 'opened', {
+              'accept-field-states': Drupal.quickedit.app.readyFieldStates
+            });
+          });
+          break;
+
+        case 'opened':
+          // Set the isDirty attribute when appropriate so that it is known when
+          // to display the "Save" button in the entity toolbar.
+          // Note that once a field has been changed, there's no way to discard
+          // that change, hence it will have to be saved into PrivateTempStore,
+          // or the in-place editing of this field will have to be stopped
+          // completely. In other words: once any field enters the 'changed'
+          // field, then for the remainder of the in-place editing session, the
+          // entity is by definition dirty.
+          if (fieldState === 'changed') {
+            entityModel.set('isDirty', true);
+          }
+          else {
+            this._updateInTempStoreAttributes(entityModel, fieldModel);
+          }
+          break;
+
+        case 'committing':
+          // If the field save returned a validation error, set the state of the
+          // entity back to 'opened'.
+          if (fieldState === 'invalid') {
+            // A state change in reaction to another state change must be
+            // deferred.
+            _.defer(function () {
+              entityModel.set('state', 'opened', {reason: 'invalid'});
+            });
+          }
+          else {
+            this._updateInTempStoreAttributes(entityModel, fieldModel);
+          }
+
+          // Attempt to save the entity. If the entity's fields are not yet all
+          // in a ready state, the save will not be processed.
+          var options = {
+            'accept-field-states': Drupal.quickedit.app.readyFieldStates
+          };
+          if (entityModel.set('isCommitting', true, options)) {
+            entityModel.save({
+              success: function () {
+                entityModel.set({
+                  state: 'deactivating',
+                  isCommitting: false
+                }, {saved: true});
+              },
+              error: function () {
+                // Reset the "isCommitting" mutex.
+                entityModel.set('isCommitting', false);
+                // Change the state back to "opened", to allow the user to hit
+                // the "Save" button again.
+                entityModel.set('state', 'opened', {reason: 'networkerror'});
+                // Show a modal to inform the user of the network error.
+                var message = Drupal.t('Your changes to <q>@entity-title</q> could not be saved, either due to a website problem or a network connection problem.<br>Please try again.', {'@entity-title': entityModel.get('label')});
+                Drupal.quickedit.util.networkErrorModal(Drupal.t('Network problem!'), message);
+              }
+            });
+          }
+          break;
+
+        case 'deactivating':
+          // When setting the entity to 'closing', require that all fieldModels
+          // are in either the 'candidate' or 'highlighted' state.
+          // A state change in reaction to another state change must be
+          // deferred.
+          _.defer(function () {
+            entityModel.set('state', 'closing', {
+              'accept-field-states': Drupal.quickedit.app.readyFieldStates
+            });
+          });
+          break;
+
+        case 'closing':
+          // When setting the entity to 'closed', require that all fieldModels
+          // are in the 'inactive' state.
+          // A state change in reaction to another state change must be
+          // deferred.
+          _.defer(function () {
+            entityModel.set('state', 'closed', {
+              'accept-field-states': ['inactive']
+            });
+          });
+          break;
+      }
+    },
+
+    /**
+     * Fires an AJAX request to the REST save URL for an entity.
+     *
+     * @param {object} options
+     *   An object of options that contains:
+     * @param {function} [options.success]
+     *   A function to invoke if the entity is successfully saved.
+     */
+    save: function (options) {
+      var entityModel = this;
+
+      // Create a Drupal.ajax instance to save the entity.
+      var entitySaverAjax = Drupal.ajax({
+        url: Drupal.url('quickedit/entity/' + entityModel.get('entityID')),
+        error: function () {
+          // Let the Drupal.quickedit.EntityModel Backbone model's error()
+          // method handle errors.
+          options.error.call(entityModel);
+        }
+      });
+      // Entity saved successfully.
+      entitySaverAjax.commands.quickeditEntitySaved = function (ajax, response, status) {
+        // All fields have been moved from PrivateTempStore to permanent
+        // storage, update the "inTempStore" attribute on FieldModels, on the
+        // EntityModel and clear EntityModel's "fieldInTempStore" attribute.
+        entityModel.get('fields').each(function (fieldModel) {
+          fieldModel.set('inTempStore', false);
+        });
+        entityModel.set('inTempStore', false);
+        entityModel.set('fieldsInTempStore', []);
+
+        // Invoke the optional success callback.
+        if (options.success) {
+          options.success.call(entityModel);
+        }
+      };
+      // Trigger the AJAX request, which will will return the
+      // quickeditEntitySaved AJAX command to which we then react.
+      entitySaverAjax.execute();
+    },
+
+    /**
+     * Validate the entity model.
+     *
+     * @param {object} attrs
+     *   The attributes changes in the save or set call.
+     * @param {object} options
+     *   An object with the following option:
+     * @param {string} [options.reason]
+     *   A string that conveys a particular reason to allow for an exceptional
+     *   state change.
+     * @param {Array} options.accept-field-states
+     *   An array of strings that represent field states that the entities must
+     *   be in to validate. For example, if `accept-field-states` is
+     *   `['candidate', 'highlighted']`, then all the fields of the entity must
+     *   be in either of these two states for the save or set call to
+     *   validate and proceed.
+     *
+     * @return {string}
+     *   A string to say something about the state of the entity model.
+     */
+    validate: function (attrs, options) {
+      var acceptedFieldStates = options['accept-field-states'] || [];
+
+      // Validate state change.
+      var currentState = this.get('state');
+      var nextState = attrs.state;
+      if (currentState !== nextState) {
+        // Ensure it's a valid state.
+        if (_.indexOf(this.constructor.states, nextState) === -1) {
+          return '"' + nextState + '" is an invalid state';
+        }
+
+        // Ensure it's a state change that is allowed.
+        // Check if the acceptStateChange function accepts it.
+        if (!this._acceptStateChange(currentState, nextState, options)) {
+          return 'state change not accepted';
+        }
+        // If that function accepts it, then ensure all fields are also in an
+        // acceptable state.
+        else if (!this._fieldsHaveAcceptableStates(acceptedFieldStates)) {
+          return 'state change not accepted because fields are not in acceptable state';
+        }
+      }
+
+      // Validate setting isCommitting = true.
+      var currentIsCommitting = this.get('isCommitting');
+      var nextIsCommitting = attrs.isCommitting;
+      if (currentIsCommitting === false && nextIsCommitting === true) {
+        if (!this._fieldsHaveAcceptableStates(acceptedFieldStates)) {
+          return 'isCommitting change not accepted because fields are not in acceptable state';
+        }
+      }
+      else if (currentIsCommitting === true && nextIsCommitting === true) {
+        return 'isCommitting is a mutex, hence only changes are allowed';
+      }
+    },
+
+    /**
+     * Checks if a state change can be accepted.
+     *
+     * @param {string} from
+     *   From state.
+     * @param {string} to
+     *   To state.
+     * @param {object} context
+     *   Context for the check.
+     * @param {string} context.reason
+     *   The reason for the state change.
+     * @param {bool} context.confirming
+     *   Whether context is confirming or not.
+     *
+     * @return {bool}
+     *   Whether the state change is accepted or not.
+     *
+     * @see Drupal.quickedit.AppView#acceptEditorStateChange
+     */
+    _acceptStateChange: function (from, to, context) {
+      var accept = true;
+
+      // In general, enforce the states sequence. Disallow going back from a
+      // "later" state to an "earlier" state, except in explicitly allowed
+      // cases.
+      if (!this.constructor.followsStateSequence(from, to)) {
+        accept = false;
+
+        // Allow: closing -> closed.
+        // Necessary to stop editing an entity.
+        if (from === 'closing' && to === 'closed') {
+          accept = true;
+        }
+        // Allow: committing -> opened.
+        // Necessary to be able to correct an invalid field, or to hit the
+        // "Save" button again after a server/network error.
+        else if (from === 'committing' && to === 'opened' && context.reason && (context.reason === 'invalid' || context.reason === 'networkerror')) {
+          accept = true;
+        }
+        // Allow: deactivating -> opened.
+        // Necessary to be able to confirm changes with the user.
+        else if (from === 'deactivating' && to === 'opened' && context.confirming) {
+          accept = true;
+        }
+        // Allow: opened -> deactivating.
+        // Necessary to be able to stop editing.
+        else if (from === 'opened' && to === 'deactivating' && context.confirmed) {
+          accept = true;
+        }
+      }
+
+      return accept;
+    },
+
+    /**
+     * Checks if fields have acceptable states.
+     *
+     * @param {Array} acceptedFieldStates
+     *   An array of acceptable field states to check for.
+     *
+     * @return {bool}
+     *   Whether the fields have an acceptable state.
+     *
+     * @see Drupal.quickedit.EntityModel#validate
+     */
+    _fieldsHaveAcceptableStates: function (acceptedFieldStates) {
+      var accept = true;
+
+      // If no acceptable field states are provided, assume all field states are
+      // acceptable. We want to let validation pass as a default and only
+      // check validity on calls to set that explicitly request it.
+      if (acceptedFieldStates.length > 0) {
+        var fieldStates = this.get('fields').pluck('state') || [];
+        // If not all fields are in one of the accepted field states, then we
+        // still can't allow this state change.
+        if (_.difference(fieldStates, acceptedFieldStates).length) {
+          accept = false;
+        }
+      }
+
+      return accept;
+    },
+
+    /**
+     * Destroys the entity model.
+     *
+     * @param {object} options
+     *   Options for the entity model.
+     */
+    destroy: function (options) {
+      Drupal.quickedit.BaseModel.prototype.destroy.call(this, options);
+
+      this.stopListening();
+
+      // Destroy all fields of this entity.
+      this.get('fields').reset();
+    },
+
+    /**
+     * @inheritdoc
+     */
+    sync: function () {
+      // We don't use REST updates to sync.
+      return;
+    }
+
+  }, /** @lends Drupal.quickedit.EntityModel */{
+
+    /**
+     * Sequence of all possible states an entity can be in during quickediting.
+     *
+     * @type {Array.<string>}
+     */
+    states: [
+      // Initial state, like field's 'inactive' OR the user has just finished
+      // in-place editing this entity.
+      // - Trigger: none (initial) or EntityModel (finished).
+      // - Expected behavior: (when not initial state): tear down
+      //   EntityToolbarView, in-place editors and related views.
+      'closed',
+      // User has activated in-place editing of this entity.
+      // - Trigger: user.
+      // - Expected behavior: the EntityToolbarView is gets set up, in-place
+      //   editors (EditorViews) and related views for this entity's fields are
+      //   set up. Upon completion of those, the state is changed to 'opening'.
+      'launching',
+      // Launching has finished.
+      // - Trigger: application.
+      // - Guarantees: in-place editors ready for use, all entity and field
+      //   views have been set up, all fields are in the 'inactive' state.
+      // - Expected behavior: all fields are changed to the 'candidate' state
+      //   and once this is completed, the entity state will be changed to
+      //   'opened'.
+      'opening',
+      // Opening has finished.
+      // - Trigger: EntityModel.
+      // - Guarantees: see 'opening', all fields are in the 'candidate' state.
+      // - Expected behavior: the user is able to actually use in-place editing.
+      'opened',
+      // User has clicked the 'Save' button (and has thus changed at least one
+      // field).
+      // - Trigger: user.
+      // - Guarantees: see 'opened', plus: either a changed field is in
+      //   PrivateTempStore, or the user has just modified a field without
+      //   activating (switching to) another field.
+      // - Expected behavior: 1) if any of the fields are not yet in
+      //   PrivateTempStore, save them to PrivateTempStore, 2) if then any of
+      //   the fields has the 'invalid' state, then change the entity state back
+      //   to 'opened', otherwise: save the entity by committing it from
+      //   PrivateTempStore into permanent storage.
+      'committing',
+      // User has clicked the 'Close' button, or has clicked the 'Save' button
+      // and that was successfully completed.
+      // - Trigger: user or EntityModel.
+      // - Guarantees: when having clicked 'Close' hardly any: fields may be in
+      //   a variety of states; when having clicked 'Save': all fields are in
+      //   the 'candidate' state.
+      // - Expected behavior: transition all fields to the 'candidate' state,
+      //   possibly requiring confirmation in the case of having clicked
+      //   'Close'.
+      'deactivating',
+      // Deactivation has been completed.
+      // - Trigger: EntityModel.
+      // - Guarantees: all fields are in the 'candidate' state.
+      // - Expected behavior: change all fields to the 'inactive' state.
+      'closing'
+    ],
+
+    /**
+     * Indicates whether the 'from' state comes before the 'to' state.
+     *
+     * @param {string} from
+     *   One of {@link Drupal.quickedit.EntityModel.states}.
+     * @param {string} to
+     *   One of {@link Drupal.quickedit.EntityModel.states}.
+     *
+     * @return {bool}
+     *   Whether the 'from' state comes before the 'to' state.
+     */
+    followsStateSequence: function (from, to) {
+      return _.indexOf(this.states, from) < _.indexOf(this.states, to);
+    }
+
+  });
+
+  /**
+   * @constructor
+   *
+   * @augments Backbone.Collection
+   */
+  Drupal.quickedit.EntityCollection = Backbone.Collection.extend(/** @lends Drupal.quickedit.EntityCollection# */{
+
+    /**
+     * @type {Drupal.quickedit.EntityModel}
+     */
+    model: Drupal.quickedit.EntityModel
+  });
+
+}(_, jQuery, Backbone, Drupal));
diff --git a/core/modules/quickedit/js/models/EntityModel.js b/core/modules/quickedit/js/models/EntityModel.js
index f444fbb91768..ebea7a36b0c6 100644
--- a/core/modules/quickedit/js/models/EntityModel.js
+++ b/core/modules/quickedit/js/models/EntityModel.js
@@ -1,172 +1,55 @@
 /**
- * @file
- * A Backbone Model for the state of an in-place editable entity in the DOM.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/quickedit/js/models/EntityModel.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function (_, $, Backbone, Drupal) {
 
   'use strict';
 
-  Drupal.quickedit.EntityModel = Drupal.quickedit.BaseModel.extend(/** @lends Drupal.quickedit.EntityModel# */{
-
-    /**
-     * @type {object}
-     */
-    defaults: /** @lends Drupal.quickedit.EntityModel# */{
-
-      /**
-       * The DOM element that represents this entity.
-       *
-       * It may seem bizarre to have a DOM element in a Backbone Model, but we
-       * need to be able to map entities in the DOM to EntityModels in memory.
-       *
-       * @type {HTMLElement}
-       */
+  Drupal.quickedit.EntityModel = Drupal.quickedit.BaseModel.extend({
+    defaults: {
       el: null,
 
-      /**
-       * An entity ID, of the form `<entity type>/<entity ID>`
-       *
-       * @example
-       * "node/1"
-       *
-       * @type {string}
-       */
       entityID: null,
 
-      /**
-       * An entity instance ID.
-       *
-       * The first instance of a specific entity (i.e. with a given entity ID)
-       * is assigned 0, the second 1, and so on.
-       *
-       * @type {number}
-       */
       entityInstanceID: null,
 
-      /**
-       * The unique ID of this entity instance on the page, of the form
-       * `<entity type>/<entity ID>[entity instance ID]`
-       *
-       * @example
-       * "node/1[0]"
-       *
-       * @type {string}
-       */
       id: null,
 
-      /**
-       * The label of the entity.
-       *
-       * @type {string}
-       */
       label: null,
 
-      /**
-       * A FieldCollection for all fields of the entity.
-       *
-       * @type {Drupal.quickedit.FieldCollection}
-       *
-       * @see Drupal.quickedit.FieldCollection
-       */
       fields: null,
 
-      // The attributes below are stateful. The ones above will never change
-      // during the life of a EntityModel instance.
-
-      /**
-       * Indicates whether this entity is currently being edited in-place.
-       *
-       * @type {bool}
-       */
       isActive: false,
 
-      /**
-       * Whether one or more fields are already been stored in PrivateTempStore.
-       *
-       * @type {bool}
-       */
       inTempStore: false,
 
-      /**
-       * Indicates whether a "Save" button is necessary or not.
-       *
-       * Whether one or more fields have already been stored in PrivateTempStore
-       * *or* the field that's currently being edited is in the 'changed' or a
-       * later state.
-       *
-       * @type {bool}
-       */
       isDirty: false,
 
-      /**
-       * Whether the request to the server has been made to commit this entity.
-       *
-       * Used to prevent multiple such requests.
-       *
-       * @type {bool}
-       */
       isCommitting: false,
 
-      /**
-       * The current processing state of an entity.
-       *
-       * @type {string}
-       */
       state: 'closed',
 
-      /**
-       * IDs of fields whose new values have been stored in PrivateTempStore.
-       *
-       * We must store this on the EntityModel as well (even though it already
-       * is on the FieldModel) because when a field is rerendered, its
-       * FieldModel is destroyed and this allows us to transition it back to
-       * the proper state.
-       *
-       * @type {Array.<string>}
-       */
       fieldsInTempStore: [],
 
-      /**
-       * A flag the tells the application that this EntityModel must be reloaded
-       * in order to restore the original values to its fields in the client.
-       *
-       * @type {bool}
-       */
       reload: false
     },
 
-    /**
-     * @constructs
-     *
-     * @augments Drupal.quickedit.BaseModel
-     */
-    initialize: function () {
+    initialize: function initialize() {
       this.set('fields', new Drupal.quickedit.FieldCollection());
 
-      // Respond to entity state changes.
       this.listenTo(this, 'change:state', this.stateChange);
 
-      // The state of the entity is largely dependent on the state of its
-      // fields.
       this.listenTo(this.get('fields'), 'change:state', this.fieldStateChange);
 
-      // Call Drupal.quickedit.BaseModel's initialize() method.
       Drupal.quickedit.BaseModel.prototype.initialize.call(this);
     },
 
-    /**
-     * Updates FieldModels' states when an EntityModel change occurs.
-     *
-     * @param {Drupal.quickedit.EntityModel} entityModel
-     *   The entity model
-     * @param {string} state
-     *   The state of the associated entity. One of
-     *   {@link Drupal.quickedit.EntityModel.states}.
-     * @param {object} options
-     *   Options for the entity model.
-     */
-    stateChange: function (entityModel, state, options) {
+    stateChange: function stateChange(entityModel, state, options) {
       var to = state;
       switch (to) {
         case 'closed':
@@ -181,81 +64,53 @@
           break;
 
         case 'opening':
-          // Set the fields to candidate state.
           entityModel.get('fields').each(function (fieldModel) {
             fieldModel.set('state', 'candidate', options);
           });
           break;
 
         case 'opened':
-          // The entity is now ready for editing!
           this.set('isActive', true);
           break;
 
         case 'committing':
-          // The user indicated they want to save the entity.
           var fields = this.get('fields');
-          // For fields that are in an active state, transition them to
-          // candidate.
-          fields.chain()
-            .filter(function (fieldModel) {
-              return _.intersection([fieldModel.get('state')], ['active']).length;
-            })
-            .each(function (fieldModel) {
-              fieldModel.set('state', 'candidate');
-            });
-          // For fields that are in a changed state, field values must first be
-          // stored in PrivateTempStore.
-          fields.chain()
-            .filter(function (fieldModel) {
-              return _.intersection([fieldModel.get('state')], Drupal.quickedit.app.changedFieldStates).length;
-            })
-            .each(function (fieldModel) {
-              fieldModel.set('state', 'saving');
-            });
+
+          fields.chain().filter(function (fieldModel) {
+            return _.intersection([fieldModel.get('state')], ['active']).length;
+          }).each(function (fieldModel) {
+            fieldModel.set('state', 'candidate');
+          });
+
+          fields.chain().filter(function (fieldModel) {
+            return _.intersection([fieldModel.get('state')], Drupal.quickedit.app.changedFieldStates).length;
+          }).each(function (fieldModel) {
+            fieldModel.set('state', 'saving');
+          });
           break;
 
         case 'deactivating':
-          var changedFields = this.get('fields')
-            .filter(function (fieldModel) {
-              return _.intersection([fieldModel.get('state')], ['changed', 'invalid']).length;
-            });
-          // If the entity contains unconfirmed or unsaved changes, return the
-          // entity to an opened state and ask the user if they would like to
-          // save the changes or discard the changes.
-          //   1. One of the fields is in a changed state. The changed field
-          //   might just be a change in the client or it might have been saved
-          //   to tempstore.
-          //   2. The saved flag is empty and the confirmed flag is empty. If
-          //   the entity has been saved to the server, the fields changed in
-          //   the client are irrelevant. If the changes are confirmed, then
-          //   proceed to set the fields to candidate state.
-          if ((changedFields.length || this.get('fieldsInTempStore').length) && (!options.saved && !options.confirmed)) {
-            // Cancel deactivation until the user confirms save or discard.
-            this.set('state', 'opened', {confirming: true});
-            // An action in reaction to state change must be deferred.
+          var changedFields = this.get('fields').filter(function (fieldModel) {
+            return _.intersection([fieldModel.get('state')], ['changed', 'invalid']).length;
+          });
+
+          if ((changedFields.length || this.get('fieldsInTempStore').length) && !options.saved && !options.confirmed) {
+            this.set('state', 'opened', { confirming: true });
+
             _.defer(function () {
               Drupal.quickedit.app.confirmEntityDeactivation(entityModel);
             });
-          }
-          else {
-            var invalidFields = this.get('fields')
-              .filter(function (fieldModel) {
-                return _.intersection([fieldModel.get('state')], ['invalid']).length;
-              });
-            // Indicate if this EntityModel needs to be reloaded in order to
-            // restore the original values of its fields.
-            entityModel.set('reload', (this.get('fieldsInTempStore').length || invalidFields.length));
-            // Set all fields to the 'candidate' state. A changed field may have
-            // to go through confirmation first.
+          } else {
+            var invalidFields = this.get('fields').filter(function (fieldModel) {
+              return _.intersection([fieldModel.get('state')], ['invalid']).length;
+            });
+
+            entityModel.set('reload', this.get('fieldsInTempStore').length || invalidFields.length);
+
             entityModel.get('fields').each(function (fieldModel) {
-              // If the field is already in the candidate state, trigger a
-              // change event so that the entityModel can move to the next state
-              // in deactivation.
               if (_.intersection([fieldModel.get('state')], ['candidate', 'highlighted']).length) {
                 fieldModel.trigger('change:state', fieldModel, fieldModel.get('state'), options);
-              }
-              else {
+              } else {
                 fieldModel.set('state', 'candidate', options);
               }
             });
@@ -263,7 +118,6 @@
           break;
 
         case 'closing':
-          // Set all fields to the 'inactive' state.
           options.reason = 'stop';
           this.get('fields').each(function (fieldModel) {
             fieldModel.set({
@@ -275,82 +129,34 @@
       }
     },
 
-    /**
-     * Updates a Field and Entity model's "inTempStore" when appropriate.
-     *
-     * Helper function.
-     *
-     * @param {Drupal.quickedit.EntityModel} entityModel
-     *   The model of the entity for which a field's state attribute has
-     *   changed.
-     * @param {Drupal.quickedit.FieldModel} fieldModel
-     *   The model of the field whose state attribute has changed.
-     *
-     * @see Drupal.quickedit.EntityModel#fieldStateChange
-     */
-    _updateInTempStoreAttributes: function (entityModel, fieldModel) {
+    _updateInTempStoreAttributes: function _updateInTempStoreAttributes(entityModel, fieldModel) {
       var current = fieldModel.get('state');
       var previous = fieldModel.previous('state');
       var fieldsInTempStore = entityModel.get('fieldsInTempStore');
-      // If the fieldModel changed to the 'saved' state: remember that this
-      // field was saved to PrivateTempStore.
+
       if (current === 'saved') {
-        // Mark the entity as saved in PrivateTempStore, so that we can pass the
-        // proper "reset PrivateTempStore" boolean value when communicating with
-        // the server.
         entityModel.set('inTempStore', true);
-        // Mark the field as saved in PrivateTempStore, so that visual
-        // indicators signifying just that may be rendered.
+
         fieldModel.set('inTempStore', true);
-        // Remember that this field is in PrivateTempStore, restore when
-        // rerendered.
+
         fieldsInTempStore.push(fieldModel.get('fieldID'));
         fieldsInTempStore = _.uniq(fieldsInTempStore);
         entityModel.set('fieldsInTempStore', fieldsInTempStore);
-      }
-      // If the fieldModel changed to the 'candidate' state from the
-      // 'inactive' state, then this is a field for this entity that got
-      // rerendered. Restore its previous 'inTempStore' attribute value.
-      else if (current === 'candidate' && previous === 'inactive') {
-        fieldModel.set('inTempStore', _.intersection([fieldModel.get('fieldID')], fieldsInTempStore).length > 0);
-      }
+      } else if (current === 'candidate' && previous === 'inactive') {
+          fieldModel.set('inTempStore', _.intersection([fieldModel.get('fieldID')], fieldsInTempStore).length > 0);
+        }
     },
 
-    /**
-     * Reacts to state changes in this entity's fields.
-     *
-     * @param {Drupal.quickedit.FieldModel} fieldModel
-     *   The model of the field whose state attribute changed.
-     * @param {string} state
-     *   The state of the associated field. One of
-     *   {@link Drupal.quickedit.FieldModel.states}.
-     */
-    fieldStateChange: function (fieldModel, state) {
+    fieldStateChange: function fieldStateChange(fieldModel, state) {
       var entityModel = this;
       var fieldState = state;
-      // Switch on the entityModel state.
-      // The EntityModel responds to FieldModel state changes as a function of
-      // its state. For example, a field switching back to 'candidate' state
-      // when its entity is in the 'opened' state has no effect on the entity.
-      // But that same switch back to 'candidate' state of a field when the
-      // entity is in the 'committing' state might allow the entity to proceed
-      // with the commit flow.
+
       switch (this.get('state')) {
         case 'closed':
         case 'launching':
-          // It should be impossible to reach these: fields can't change state
-          // while the entity is closed or still launching.
           break;
 
         case 'opening':
-          // We must change the entity to the 'opened' state, but it must first
-          // be confirmed that all of its fieldModels have transitioned to the
-          // 'candidate' state.
-          // We do this here, because this is called every time a fieldModel
-          // changes state, hence each time this is called, we get closer to the
-          // goal of having all fieldModels in the 'candidate' state.
-          // A state change in reaction to another state change must be
-          // deferred.
           _.defer(function () {
             entityModel.set('state', 'opened', {
               'accept-field-states': Drupal.quickedit.app.readyFieldStates
@@ -359,57 +165,39 @@
           break;
 
         case 'opened':
-          // Set the isDirty attribute when appropriate so that it is known when
-          // to display the "Save" button in the entity toolbar.
-          // Note that once a field has been changed, there's no way to discard
-          // that change, hence it will have to be saved into PrivateTempStore,
-          // or the in-place editing of this field will have to be stopped
-          // completely. In other words: once any field enters the 'changed'
-          // field, then for the remainder of the in-place editing session, the
-          // entity is by definition dirty.
           if (fieldState === 'changed') {
             entityModel.set('isDirty', true);
-          }
-          else {
+          } else {
             this._updateInTempStoreAttributes(entityModel, fieldModel);
           }
           break;
 
         case 'committing':
-          // If the field save returned a validation error, set the state of the
-          // entity back to 'opened'.
           if (fieldState === 'invalid') {
-            // A state change in reaction to another state change must be
-            // deferred.
             _.defer(function () {
-              entityModel.set('state', 'opened', {reason: 'invalid'});
+              entityModel.set('state', 'opened', { reason: 'invalid' });
             });
-          }
-          else {
+          } else {
             this._updateInTempStoreAttributes(entityModel, fieldModel);
           }
 
-          // Attempt to save the entity. If the entity's fields are not yet all
-          // in a ready state, the save will not be processed.
           var options = {
             'accept-field-states': Drupal.quickedit.app.readyFieldStates
           };
           if (entityModel.set('isCommitting', true, options)) {
             entityModel.save({
-              success: function () {
+              success: function success() {
                 entityModel.set({
                   state: 'deactivating',
                   isCommitting: false
-                }, {saved: true});
+                }, { saved: true });
               },
-              error: function () {
-                // Reset the "isCommitting" mutex.
+              error: function error() {
                 entityModel.set('isCommitting', false);
-                // Change the state back to "opened", to allow the user to hit
-                // the "Save" button again.
-                entityModel.set('state', 'opened', {reason: 'networkerror'});
-                // Show a modal to inform the user of the network error.
-                var message = Drupal.t('Your changes to <q>@entity-title</q> could not be saved, either due to a website problem or a network connection problem.<br>Please try again.', {'@entity-title': entityModel.get('label')});
+
+                entityModel.set('state', 'opened', { reason: 'networkerror' });
+
+                var message = Drupal.t('Your changes to <q>@entity-title</q> could not be saved, either due to a website problem or a network connection problem.<br>Please try again.', { '@entity-title': entityModel.get('label') });
                 Drupal.quickedit.util.networkErrorModal(Drupal.t('Network problem!'), message);
               }
             });
@@ -417,10 +205,6 @@
           break;
 
         case 'deactivating':
-          // When setting the entity to 'closing', require that all fieldModels
-          // are in either the 'candidate' or 'highlighted' state.
-          // A state change in reaction to another state change must be
-          // deferred.
           _.defer(function () {
             entityModel.set('state', 'closing', {
               'accept-field-states': Drupal.quickedit.app.readyFieldStates
@@ -429,10 +213,6 @@
           break;
 
         case 'closing':
-          // When setting the entity to 'closed', require that all fieldModels
-          // are in the 'inactive' state.
-          // A state change in reaction to another state change must be
-          // deferred.
           _.defer(function () {
             entityModel.set('state', 'closed', {
               'accept-field-states': ['inactive']
@@ -442,179 +222,85 @@
       }
     },
 
-    /**
-     * Fires an AJAX request to the REST save URL for an entity.
-     *
-     * @param {object} options
-     *   An object of options that contains:
-     * @param {function} [options.success]
-     *   A function to invoke if the entity is successfully saved.
-     */
-    save: function (options) {
+    save: function save(options) {
       var entityModel = this;
 
-      // Create a Drupal.ajax instance to save the entity.
       var entitySaverAjax = Drupal.ajax({
         url: Drupal.url('quickedit/entity/' + entityModel.get('entityID')),
-        error: function () {
-          // Let the Drupal.quickedit.EntityModel Backbone model's error()
-          // method handle errors.
+        error: function error() {
           options.error.call(entityModel);
         }
       });
-      // Entity saved successfully.
+
       entitySaverAjax.commands.quickeditEntitySaved = function (ajax, response, status) {
-        // All fields have been moved from PrivateTempStore to permanent
-        // storage, update the "inTempStore" attribute on FieldModels, on the
-        // EntityModel and clear EntityModel's "fieldInTempStore" attribute.
         entityModel.get('fields').each(function (fieldModel) {
           fieldModel.set('inTempStore', false);
         });
         entityModel.set('inTempStore', false);
         entityModel.set('fieldsInTempStore', []);
 
-        // Invoke the optional success callback.
         if (options.success) {
           options.success.call(entityModel);
         }
       };
-      // Trigger the AJAX request, which will will return the
-      // quickeditEntitySaved AJAX command to which we then react.
+
       entitySaverAjax.execute();
     },
 
-    /**
-     * Validate the entity model.
-     *
-     * @param {object} attrs
-     *   The attributes changes in the save or set call.
-     * @param {object} options
-     *   An object with the following option:
-     * @param {string} [options.reason]
-     *   A string that conveys a particular reason to allow for an exceptional
-     *   state change.
-     * @param {Array} options.accept-field-states
-     *   An array of strings that represent field states that the entities must
-     *   be in to validate. For example, if `accept-field-states` is
-     *   `['candidate', 'highlighted']`, then all the fields of the entity must
-     *   be in either of these two states for the save or set call to
-     *   validate and proceed.
-     *
-     * @return {string}
-     *   A string to say something about the state of the entity model.
-     */
-    validate: function (attrs, options) {
+    validate: function validate(attrs, options) {
       var acceptedFieldStates = options['accept-field-states'] || [];
 
-      // Validate state change.
       var currentState = this.get('state');
       var nextState = attrs.state;
       if (currentState !== nextState) {
-        // Ensure it's a valid state.
         if (_.indexOf(this.constructor.states, nextState) === -1) {
           return '"' + nextState + '" is an invalid state';
         }
 
-        // Ensure it's a state change that is allowed.
-        // Check if the acceptStateChange function accepts it.
         if (!this._acceptStateChange(currentState, nextState, options)) {
           return 'state change not accepted';
-        }
-        // If that function accepts it, then ensure all fields are also in an
-        // acceptable state.
-        else if (!this._fieldsHaveAcceptableStates(acceptedFieldStates)) {
-          return 'state change not accepted because fields are not in acceptable state';
-        }
+        } else if (!this._fieldsHaveAcceptableStates(acceptedFieldStates)) {
+            return 'state change not accepted because fields are not in acceptable state';
+          }
       }
 
-      // Validate setting isCommitting = true.
       var currentIsCommitting = this.get('isCommitting');
       var nextIsCommitting = attrs.isCommitting;
       if (currentIsCommitting === false && nextIsCommitting === true) {
         if (!this._fieldsHaveAcceptableStates(acceptedFieldStates)) {
           return 'isCommitting change not accepted because fields are not in acceptable state';
         }
-      }
-      else if (currentIsCommitting === true && nextIsCommitting === true) {
+      } else if (currentIsCommitting === true && nextIsCommitting === true) {
         return 'isCommitting is a mutex, hence only changes are allowed';
       }
     },
 
-    /**
-     * Checks if a state change can be accepted.
-     *
-     * @param {string} from
-     *   From state.
-     * @param {string} to
-     *   To state.
-     * @param {object} context
-     *   Context for the check.
-     * @param {string} context.reason
-     *   The reason for the state change.
-     * @param {bool} context.confirming
-     *   Whether context is confirming or not.
-     *
-     * @return {bool}
-     *   Whether the state change is accepted or not.
-     *
-     * @see Drupal.quickedit.AppView#acceptEditorStateChange
-     */
-    _acceptStateChange: function (from, to, context) {
+    _acceptStateChange: function _acceptStateChange(from, to, context) {
       var accept = true;
 
-      // In general, enforce the states sequence. Disallow going back from a
-      // "later" state to an "earlier" state, except in explicitly allowed
-      // cases.
       if (!this.constructor.followsStateSequence(from, to)) {
         accept = false;
 
-        // Allow: closing -> closed.
-        // Necessary to stop editing an entity.
         if (from === 'closing' && to === 'closed') {
           accept = true;
-        }
-        // Allow: committing -> opened.
-        // Necessary to be able to correct an invalid field, or to hit the
-        // "Save" button again after a server/network error.
-        else if (from === 'committing' && to === 'opened' && context.reason && (context.reason === 'invalid' || context.reason === 'networkerror')) {
-          accept = true;
-        }
-        // Allow: deactivating -> opened.
-        // Necessary to be able to confirm changes with the user.
-        else if (from === 'deactivating' && to === 'opened' && context.confirming) {
-          accept = true;
-        }
-        // Allow: opened -> deactivating.
-        // Necessary to be able to stop editing.
-        else if (from === 'opened' && to === 'deactivating' && context.confirmed) {
-          accept = true;
-        }
+        } else if (from === 'committing' && to === 'opened' && context.reason && (context.reason === 'invalid' || context.reason === 'networkerror')) {
+            accept = true;
+          } else if (from === 'deactivating' && to === 'opened' && context.confirming) {
+              accept = true;
+            } else if (from === 'opened' && to === 'deactivating' && context.confirmed) {
+                accept = true;
+              }
       }
 
       return accept;
     },
 
-    /**
-     * Checks if fields have acceptable states.
-     *
-     * @param {Array} acceptedFieldStates
-     *   An array of acceptable field states to check for.
-     *
-     * @return {bool}
-     *   Whether the fields have an acceptable state.
-     *
-     * @see Drupal.quickedit.EntityModel#validate
-     */
-    _fieldsHaveAcceptableStates: function (acceptedFieldStates) {
+    _fieldsHaveAcceptableStates: function _fieldsHaveAcceptableStates(acceptedFieldStates) {
       var accept = true;
 
-      // If no acceptable field states are provided, assume all field states are
-      // acceptable. We want to let validation pass as a default and only
-      // check validity on calls to set that explicitly request it.
       if (acceptedFieldStates.length > 0) {
         var fieldStates = this.get('fields').pluck('state') || [];
-        // If not all fields are in one of the accepted field states, then we
-        // still can't allow this state change.
+
         if (_.difference(fieldStates, acceptedFieldStates).length) {
           accept = false;
         }
@@ -623,119 +309,28 @@
       return accept;
     },
 
-    /**
-     * Destroys the entity model.
-     *
-     * @param {object} options
-     *   Options for the entity model.
-     */
-    destroy: function (options) {
+    destroy: function destroy(options) {
       Drupal.quickedit.BaseModel.prototype.destroy.call(this, options);
 
       this.stopListening();
 
-      // Destroy all fields of this entity.
       this.get('fields').reset();
     },
 
-    /**
-     * @inheritdoc
-     */
-    sync: function () {
-      // We don't use REST updates to sync.
+    sync: function sync() {
       return;
     }
 
-  }, /** @lends Drupal.quickedit.EntityModel */{
-
-    /**
-     * Sequence of all possible states an entity can be in during quickediting.
-     *
-     * @type {Array.<string>}
-     */
-    states: [
-      // Initial state, like field's 'inactive' OR the user has just finished
-      // in-place editing this entity.
-      // - Trigger: none (initial) or EntityModel (finished).
-      // - Expected behavior: (when not initial state): tear down
-      //   EntityToolbarView, in-place editors and related views.
-      'closed',
-      // User has activated in-place editing of this entity.
-      // - Trigger: user.
-      // - Expected behavior: the EntityToolbarView is gets set up, in-place
-      //   editors (EditorViews) and related views for this entity's fields are
-      //   set up. Upon completion of those, the state is changed to 'opening'.
-      'launching',
-      // Launching has finished.
-      // - Trigger: application.
-      // - Guarantees: in-place editors ready for use, all entity and field
-      //   views have been set up, all fields are in the 'inactive' state.
-      // - Expected behavior: all fields are changed to the 'candidate' state
-      //   and once this is completed, the entity state will be changed to
-      //   'opened'.
-      'opening',
-      // Opening has finished.
-      // - Trigger: EntityModel.
-      // - Guarantees: see 'opening', all fields are in the 'candidate' state.
-      // - Expected behavior: the user is able to actually use in-place editing.
-      'opened',
-      // User has clicked the 'Save' button (and has thus changed at least one
-      // field).
-      // - Trigger: user.
-      // - Guarantees: see 'opened', plus: either a changed field is in
-      //   PrivateTempStore, or the user has just modified a field without
-      //   activating (switching to) another field.
-      // - Expected behavior: 1) if any of the fields are not yet in
-      //   PrivateTempStore, save them to PrivateTempStore, 2) if then any of
-      //   the fields has the 'invalid' state, then change the entity state back
-      //   to 'opened', otherwise: save the entity by committing it from
-      //   PrivateTempStore into permanent storage.
-      'committing',
-      // User has clicked the 'Close' button, or has clicked the 'Save' button
-      // and that was successfully completed.
-      // - Trigger: user or EntityModel.
-      // - Guarantees: when having clicked 'Close' hardly any: fields may be in
-      //   a variety of states; when having clicked 'Save': all fields are in
-      //   the 'candidate' state.
-      // - Expected behavior: transition all fields to the 'candidate' state,
-      //   possibly requiring confirmation in the case of having clicked
-      //   'Close'.
-      'deactivating',
-      // Deactivation has been completed.
-      // - Trigger: EntityModel.
-      // - Guarantees: all fields are in the 'candidate' state.
-      // - Expected behavior: change all fields to the 'inactive' state.
-      'closing'
-    ],
-
-    /**
-     * Indicates whether the 'from' state comes before the 'to' state.
-     *
-     * @param {string} from
-     *   One of {@link Drupal.quickedit.EntityModel.states}.
-     * @param {string} to
-     *   One of {@link Drupal.quickedit.EntityModel.states}.
-     *
-     * @return {bool}
-     *   Whether the 'from' state comes before the 'to' state.
-     */
-    followsStateSequence: function (from, to) {
+  }, {
+    states: ['closed', 'launching', 'opening', 'opened', 'committing', 'deactivating', 'closing'],
+
+    followsStateSequence: function followsStateSequence(from, to) {
       return _.indexOf(this.states, from) < _.indexOf(this.states, to);
     }
 
   });
 
-  /**
-   * @constructor
-   *
-   * @augments Backbone.Collection
-   */
-  Drupal.quickedit.EntityCollection = Backbone.Collection.extend(/** @lends Drupal.quickedit.EntityCollection# */{
-
-    /**
-     * @type {Drupal.quickedit.EntityModel}
-     */
+  Drupal.quickedit.EntityCollection = Backbone.Collection.extend({
     model: Drupal.quickedit.EntityModel
   });
-
-}(_, jQuery, Backbone, Drupal));
+})(_, jQuery, Backbone, Drupal);
\ No newline at end of file
diff --git a/core/modules/quickedit/js/models/FieldModel.es6.js b/core/modules/quickedit/js/models/FieldModel.es6.js
new file mode 100644
index 000000000000..8aeff107d9c1
--- /dev/null
+++ b/core/modules/quickedit/js/models/FieldModel.es6.js
@@ -0,0 +1,348 @@
+/**
+ * @file
+ * A Backbone Model for the state of an in-place editable field in the DOM.
+ */
+
+(function (_, Backbone, Drupal) {
+
+  'use strict';
+
+  Drupal.quickedit.FieldModel = Drupal.quickedit.BaseModel.extend(/** @lends Drupal.quickedit.FieldModel# */{
+
+    /**
+     * @type {object}
+     */
+    defaults: /** @lends Drupal.quickedit.FieldModel# */{
+
+      /**
+       * The DOM element that represents this field. It may seem bizarre to have
+       * a DOM element in a Backbone Model, but we need to be able to map fields
+       * in the DOM to FieldModels in memory.
+       */
+      el: null,
+
+      /**
+       * A field ID, of the form
+       * `<entity type>/<id>/<field name>/<language>/<view mode>`
+       *
+       * @example
+       * "node/1/field_tags/und/full"
+       */
+      fieldID: null,
+
+      /**
+       * The unique ID of this field within its entity instance on the page, of
+       * the form `<entity type>/<id>/<field name>/<language>/<view
+       * mode>[entity instance ID]`.
+       *
+       * @example
+       * "node/1/field_tags/und/full[0]"
+       */
+      id: null,
+
+      /**
+       * A {@link Drupal.quickedit.EntityModel}. Its "fields" attribute, which
+       * is a FieldCollection, is automatically updated to include this
+       * FieldModel.
+       */
+      entity: null,
+
+      /**
+       * This field's metadata as returned by the
+       * QuickEditController::metadata().
+       */
+      metadata: null,
+
+      /**
+       * Callback function for validating changes between states. Receives the
+       * previous state, new state, context, and a callback.
+       */
+      acceptStateChange: null,
+
+      /**
+       * A logical field ID, of the form
+       * `<entity type>/<id>/<field name>/<language>`, i.e. the fieldID without
+       * the view mode, to be able to identify other instances of the same
+       * field on the page but rendered in a different view mode.
+       *
+       * @example
+       * "node/1/field_tags/und".
+       */
+      logicalFieldID: null,
+
+      // The attributes below are stateful. The ones above will never change
+      // during the life of a FieldModel instance.
+
+      /**
+       * In-place editing state of this field. Defaults to the initial state.
+       * Possible values: {@link Drupal.quickedit.FieldModel.states}.
+       */
+      state: 'inactive',
+
+      /**
+       * The field is currently in the 'changed' state or one of the following
+       * states in which the field is still changed.
+       */
+      isChanged: false,
+
+      /**
+       * Is tracked by the EntityModel, is mirrored here solely for decorative
+       * purposes: so that FieldDecorationView.renderChanged() can react to it.
+       */
+      inTempStore: false,
+
+      /**
+       * The full HTML representation of this field (with the element that has
+       * the data-quickedit-field-id as the outer element). Used to propagate
+       * changes from this field to other instances of the same field storage.
+       */
+      html: null,
+
+      /**
+       * An object containing the full HTML representations (values) of other
+       * view modes (keys) of this field, for other instances of this field
+       * displayed in a different view mode.
+       */
+      htmlForOtherViewModes: null
+    },
+
+    /**
+     * State of an in-place editable field in the DOM.
+     *
+     * @constructs
+     *
+     * @augments Drupal.quickedit.BaseModel
+     *
+     * @param {object} options
+     *   Options for the field model.
+     */
+    initialize: function (options) {
+      // Store the original full HTML representation of this field.
+      this.set('html', options.el.outerHTML);
+
+      // Enlist field automatically in the associated entity's field collection.
+      this.get('entity').get('fields').add(this);
+
+      // Automatically generate the logical field ID.
+      this.set('logicalFieldID', this.get('fieldID').split('/').slice(0, 4).join('/'));
+
+      // Call Drupal.quickedit.BaseModel's initialize() method.
+      Drupal.quickedit.BaseModel.prototype.initialize.call(this, options);
+    },
+
+    /**
+     * Destroys the field model.
+     *
+     * @param {object} options
+     *   Options for the field model.
+     */
+    destroy: function (options) {
+      if (this.get('state') !== 'inactive') {
+        throw new Error('FieldModel cannot be destroyed if it is not inactive state.');
+      }
+      Drupal.quickedit.BaseModel.prototype.destroy.call(this, options);
+    },
+
+    /**
+     * @inheritdoc
+     */
+    sync: function () {
+      // We don't use REST updates to sync.
+      return;
+    },
+
+    /**
+     * Validate function for the field model.
+     *
+     * @param {object} attrs
+     *   The attributes changes in the save or set call.
+     * @param {object} options
+     *   An object with the following option:
+     * @param {string} [options.reason]
+     *   A string that conveys a particular reason to allow for an exceptional
+     *   state change.
+     * @param {Array} options.accept-field-states
+     *   An array of strings that represent field states that the entities must
+     *   be in to validate. For example, if `accept-field-states` is
+     *   `['candidate', 'highlighted']`, then all the fields of the entity must
+     *   be in either of these two states for the save or set call to
+     *   validate and proceed.
+     *
+     * @return {string}
+     *   A string to say something about the state of the field model.
+     */
+    validate: function (attrs, options) {
+      var current = this.get('state');
+      var next = attrs.state;
+      if (current !== next) {
+        // Ensure it's a valid state.
+        if (_.indexOf(this.constructor.states, next) === -1) {
+          return '"' + next + '" is an invalid state';
+        }
+        // Check if the acceptStateChange callback accepts it.
+        if (!this.get('acceptStateChange')(current, next, options, this)) {
+          return 'state change not accepted';
+        }
+      }
+    },
+
+    /**
+     * Extracts the entity ID from this field's ID.
+     *
+     * @return {string}
+     *   An entity ID: a string of the format `<entity type>/<id>`.
+     */
+    getEntityID: function () {
+      return this.get('fieldID').split('/').slice(0, 2).join('/');
+    },
+
+    /**
+     * Extracts the view mode ID from this field's ID.
+     *
+     * @return {string}
+     *   A view mode ID.
+     */
+    getViewMode: function () {
+      return this.get('fieldID').split('/').pop();
+    },
+
+    /**
+     * Find other instances of this field with different view modes.
+     *
+     * @return {Array}
+     *   An array containing view mode IDs.
+     */
+    findOtherViewModes: function () {
+      var currentField = this;
+      var otherViewModes = [];
+      Drupal.quickedit.collections.fields
+        // Find all instances of fields that display the same logical field
+        // (same entity, same field, just a different instance and maybe a
+        // different view mode).
+        .where({logicalFieldID: currentField.get('logicalFieldID')})
+        .forEach(function (field) {
+          // Ignore the current field.
+          if (field === currentField) {
+            return;
+          }
+          // Also ignore other fields with the same view mode.
+          else if (field.get('fieldID') === currentField.get('fieldID')) {
+            return;
+          }
+          else {
+            otherViewModes.push(field.getViewMode());
+          }
+        });
+      return otherViewModes;
+    }
+
+  }, /** @lends Drupal.quickedit.FieldModel */{
+
+    /**
+     * Sequence of all possible states a field can be in during quickediting.
+     *
+     * @type {Array.<string>}
+     */
+    states: [
+      // The field associated with this FieldModel is linked to an EntityModel;
+      // the user can choose to start in-place editing that entity (and
+      // consequently this field). No in-place editor (EditorView) is associated
+      // with this field, because this field is not being in-place edited.
+      // This is both the initial (not yet in-place editing) and the end state
+      // (finished in-place editing).
+      'inactive',
+      // The user is in-place editing this entity, and this field is a
+      // candidate
+      // for in-place editing. In-place editor should not
+      // - Trigger: user.
+      // - Guarantees: entity is ready, in-place editor (EditorView) is
+      //   associated with the field.
+      // - Expected behavior: visual indicators
+      //   around the field indicate it is available for in-place editing, no
+      //   in-place editor presented yet.
+      'candidate',
+      // User is highlighting this field.
+      // - Trigger: user.
+      // - Guarantees: see 'candidate'.
+      // - Expected behavior: visual indicators to convey highlighting, in-place
+      //   editing toolbar shows field's label.
+      'highlighted',
+      // User has activated the in-place editing of this field; in-place editor
+      // is activating.
+      // - Trigger: user.
+      // - Guarantees: see 'candidate'.
+      // - Expected behavior: loading indicator, in-place editor is loading
+      //   remote data (e.g. retrieve form from back-end). Upon retrieval of
+      //   remote data, the in-place editor transitions the field's state to
+      //   'active'.
+      'activating',
+      // In-place editor has finished loading remote data; ready for use.
+      // - Trigger: in-place editor.
+      // - Guarantees: see 'candidate'.
+      // - Expected behavior: in-place editor for the field is ready for use.
+      'active',
+      // User has modified values in the in-place editor.
+      // - Trigger: user.
+      // - Guarantees: see 'candidate', plus in-place editor is ready for use.
+      // - Expected behavior: visual indicator of change.
+      'changed',
+      // User is saving changed field data in in-place editor to
+      // PrivateTempStore. The save mechanism of the in-place editor is called.
+      // - Trigger: user.
+      // - Guarantees: see 'candidate' and 'active'.
+      // - Expected behavior: saving indicator, in-place editor is saving field
+      //   data into PrivateTempStore. Upon successful saving (without
+      //   validation errors), the in-place editor transitions the field's state
+      //   to 'saved', but to 'invalid' upon failed saving (with validation
+      //   errors).
+      'saving',
+      // In-place editor has successfully saved the changed field.
+      // - Trigger: in-place editor.
+      // - Guarantees: see 'candidate' and 'active'.
+      // - Expected behavior: transition back to 'candidate' state because the
+      //   deed is done. Then: 1) transition to 'inactive' to allow the field
+      //   to be rerendered, 2) destroy the FieldModel (which also destroys
+      //   attached views like the EditorView), 3) replace the existing field
+      //   HTML with the existing HTML and 4) attach behaviors again so that the
+      //   field becomes available again for in-place editing.
+      'saved',
+      // In-place editor has failed to saved the changed field: there were
+      // validation errors.
+      // - Trigger: in-place editor.
+      // - Guarantees: see 'candidate' and 'active'.
+      // - Expected behavior: remain in 'invalid' state, let the user make more
+      //   changes so that he can save it again, without validation errors.
+      'invalid'
+    ],
+
+    /**
+     * Indicates whether the 'from' state comes before the 'to' state.
+     *
+     * @param {string} from
+     *   One of {@link Drupal.quickedit.FieldModel.states}.
+     * @param {string} to
+     *   One of {@link Drupal.quickedit.FieldModel.states}.
+     *
+     * @return {bool}
+     *   Whether the 'from' state comes before the 'to' state.
+     */
+    followsStateSequence: function (from, to) {
+      return _.indexOf(this.states, from) < _.indexOf(this.states, to);
+    }
+
+  });
+
+  /**
+   * @constructor
+   *
+   * @augments Backbone.Collection
+   */
+  Drupal.quickedit.FieldCollection = Backbone.Collection.extend(/** @lends Drupal.quickedit.FieldCollection */{
+
+    /**
+     * @type {Drupal.quickedit.FieldModel}
+     */
+    model: Drupal.quickedit.FieldModel
+  });
+
+}(_, Backbone, Drupal));
diff --git a/core/modules/quickedit/js/models/FieldModel.js b/core/modules/quickedit/js/models/FieldModel.js
index 8aeff107d9c1..305b4a381e9a 100644
--- a/core/modules/quickedit/js/models/FieldModel.js
+++ b/core/modules/quickedit/js/models/FieldModel.js
@@ -1,348 +1,110 @@
 /**
- * @file
- * A Backbone Model for the state of an in-place editable field in the DOM.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/quickedit/js/models/FieldModel.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function (_, Backbone, Drupal) {
 
   'use strict';
 
-  Drupal.quickedit.FieldModel = Drupal.quickedit.BaseModel.extend(/** @lends Drupal.quickedit.FieldModel# */{
-
-    /**
-     * @type {object}
-     */
-    defaults: /** @lends Drupal.quickedit.FieldModel# */{
-
-      /**
-       * The DOM element that represents this field. It may seem bizarre to have
-       * a DOM element in a Backbone Model, but we need to be able to map fields
-       * in the DOM to FieldModels in memory.
-       */
+  Drupal.quickedit.FieldModel = Drupal.quickedit.BaseModel.extend({
+    defaults: {
       el: null,
 
-      /**
-       * A field ID, of the form
-       * `<entity type>/<id>/<field name>/<language>/<view mode>`
-       *
-       * @example
-       * "node/1/field_tags/und/full"
-       */
       fieldID: null,
 
-      /**
-       * The unique ID of this field within its entity instance on the page, of
-       * the form `<entity type>/<id>/<field name>/<language>/<view
-       * mode>[entity instance ID]`.
-       *
-       * @example
-       * "node/1/field_tags/und/full[0]"
-       */
       id: null,
 
-      /**
-       * A {@link Drupal.quickedit.EntityModel}. Its "fields" attribute, which
-       * is a FieldCollection, is automatically updated to include this
-       * FieldModel.
-       */
       entity: null,
 
-      /**
-       * This field's metadata as returned by the
-       * QuickEditController::metadata().
-       */
       metadata: null,
 
-      /**
-       * Callback function for validating changes between states. Receives the
-       * previous state, new state, context, and a callback.
-       */
       acceptStateChange: null,
 
-      /**
-       * A logical field ID, of the form
-       * `<entity type>/<id>/<field name>/<language>`, i.e. the fieldID without
-       * the view mode, to be able to identify other instances of the same
-       * field on the page but rendered in a different view mode.
-       *
-       * @example
-       * "node/1/field_tags/und".
-       */
       logicalFieldID: null,
 
-      // The attributes below are stateful. The ones above will never change
-      // during the life of a FieldModel instance.
-
-      /**
-       * In-place editing state of this field. Defaults to the initial state.
-       * Possible values: {@link Drupal.quickedit.FieldModel.states}.
-       */
       state: 'inactive',
 
-      /**
-       * The field is currently in the 'changed' state or one of the following
-       * states in which the field is still changed.
-       */
       isChanged: false,
 
-      /**
-       * Is tracked by the EntityModel, is mirrored here solely for decorative
-       * purposes: so that FieldDecorationView.renderChanged() can react to it.
-       */
       inTempStore: false,
 
-      /**
-       * The full HTML representation of this field (with the element that has
-       * the data-quickedit-field-id as the outer element). Used to propagate
-       * changes from this field to other instances of the same field storage.
-       */
       html: null,
 
-      /**
-       * An object containing the full HTML representations (values) of other
-       * view modes (keys) of this field, for other instances of this field
-       * displayed in a different view mode.
-       */
       htmlForOtherViewModes: null
     },
 
-    /**
-     * State of an in-place editable field in the DOM.
-     *
-     * @constructs
-     *
-     * @augments Drupal.quickedit.BaseModel
-     *
-     * @param {object} options
-     *   Options for the field model.
-     */
-    initialize: function (options) {
-      // Store the original full HTML representation of this field.
+    initialize: function initialize(options) {
       this.set('html', options.el.outerHTML);
 
-      // Enlist field automatically in the associated entity's field collection.
       this.get('entity').get('fields').add(this);
 
-      // Automatically generate the logical field ID.
       this.set('logicalFieldID', this.get('fieldID').split('/').slice(0, 4).join('/'));
 
-      // Call Drupal.quickedit.BaseModel's initialize() method.
       Drupal.quickedit.BaseModel.prototype.initialize.call(this, options);
     },
 
-    /**
-     * Destroys the field model.
-     *
-     * @param {object} options
-     *   Options for the field model.
-     */
-    destroy: function (options) {
+    destroy: function destroy(options) {
       if (this.get('state') !== 'inactive') {
         throw new Error('FieldModel cannot be destroyed if it is not inactive state.');
       }
       Drupal.quickedit.BaseModel.prototype.destroy.call(this, options);
     },
 
-    /**
-     * @inheritdoc
-     */
-    sync: function () {
-      // We don't use REST updates to sync.
+    sync: function sync() {
       return;
     },
 
-    /**
-     * Validate function for the field model.
-     *
-     * @param {object} attrs
-     *   The attributes changes in the save or set call.
-     * @param {object} options
-     *   An object with the following option:
-     * @param {string} [options.reason]
-     *   A string that conveys a particular reason to allow for an exceptional
-     *   state change.
-     * @param {Array} options.accept-field-states
-     *   An array of strings that represent field states that the entities must
-     *   be in to validate. For example, if `accept-field-states` is
-     *   `['candidate', 'highlighted']`, then all the fields of the entity must
-     *   be in either of these two states for the save or set call to
-     *   validate and proceed.
-     *
-     * @return {string}
-     *   A string to say something about the state of the field model.
-     */
-    validate: function (attrs, options) {
+    validate: function validate(attrs, options) {
       var current = this.get('state');
       var next = attrs.state;
       if (current !== next) {
-        // Ensure it's a valid state.
         if (_.indexOf(this.constructor.states, next) === -1) {
           return '"' + next + '" is an invalid state';
         }
-        // Check if the acceptStateChange callback accepts it.
+
         if (!this.get('acceptStateChange')(current, next, options, this)) {
           return 'state change not accepted';
         }
       }
     },
 
-    /**
-     * Extracts the entity ID from this field's ID.
-     *
-     * @return {string}
-     *   An entity ID: a string of the format `<entity type>/<id>`.
-     */
-    getEntityID: function () {
+    getEntityID: function getEntityID() {
       return this.get('fieldID').split('/').slice(0, 2).join('/');
     },
 
-    /**
-     * Extracts the view mode ID from this field's ID.
-     *
-     * @return {string}
-     *   A view mode ID.
-     */
-    getViewMode: function () {
+    getViewMode: function getViewMode() {
       return this.get('fieldID').split('/').pop();
     },
 
-    /**
-     * Find other instances of this field with different view modes.
-     *
-     * @return {Array}
-     *   An array containing view mode IDs.
-     */
-    findOtherViewModes: function () {
+    findOtherViewModes: function findOtherViewModes() {
       var currentField = this;
       var otherViewModes = [];
-      Drupal.quickedit.collections.fields
-        // Find all instances of fields that display the same logical field
-        // (same entity, same field, just a different instance and maybe a
-        // different view mode).
-        .where({logicalFieldID: currentField.get('logicalFieldID')})
-        .forEach(function (field) {
-          // Ignore the current field.
-          if (field === currentField) {
-            return;
-          }
-          // Also ignore other fields with the same view mode.
-          else if (field.get('fieldID') === currentField.get('fieldID')) {
+      Drupal.quickedit.collections.fields.where({ logicalFieldID: currentField.get('logicalFieldID') }).forEach(function (field) {
+        if (field === currentField) {
+          return;
+        } else if (field.get('fieldID') === currentField.get('fieldID')) {
             return;
-          }
-          else {
+          } else {
             otherViewModes.push(field.getViewMode());
           }
-        });
+      });
       return otherViewModes;
     }
 
-  }, /** @lends Drupal.quickedit.FieldModel */{
-
-    /**
-     * Sequence of all possible states a field can be in during quickediting.
-     *
-     * @type {Array.<string>}
-     */
-    states: [
-      // The field associated with this FieldModel is linked to an EntityModel;
-      // the user can choose to start in-place editing that entity (and
-      // consequently this field). No in-place editor (EditorView) is associated
-      // with this field, because this field is not being in-place edited.
-      // This is both the initial (not yet in-place editing) and the end state
-      // (finished in-place editing).
-      'inactive',
-      // The user is in-place editing this entity, and this field is a
-      // candidate
-      // for in-place editing. In-place editor should not
-      // - Trigger: user.
-      // - Guarantees: entity is ready, in-place editor (EditorView) is
-      //   associated with the field.
-      // - Expected behavior: visual indicators
-      //   around the field indicate it is available for in-place editing, no
-      //   in-place editor presented yet.
-      'candidate',
-      // User is highlighting this field.
-      // - Trigger: user.
-      // - Guarantees: see 'candidate'.
-      // - Expected behavior: visual indicators to convey highlighting, in-place
-      //   editing toolbar shows field's label.
-      'highlighted',
-      // User has activated the in-place editing of this field; in-place editor
-      // is activating.
-      // - Trigger: user.
-      // - Guarantees: see 'candidate'.
-      // - Expected behavior: loading indicator, in-place editor is loading
-      //   remote data (e.g. retrieve form from back-end). Upon retrieval of
-      //   remote data, the in-place editor transitions the field's state to
-      //   'active'.
-      'activating',
-      // In-place editor has finished loading remote data; ready for use.
-      // - Trigger: in-place editor.
-      // - Guarantees: see 'candidate'.
-      // - Expected behavior: in-place editor for the field is ready for use.
-      'active',
-      // User has modified values in the in-place editor.
-      // - Trigger: user.
-      // - Guarantees: see 'candidate', plus in-place editor is ready for use.
-      // - Expected behavior: visual indicator of change.
-      'changed',
-      // User is saving changed field data in in-place editor to
-      // PrivateTempStore. The save mechanism of the in-place editor is called.
-      // - Trigger: user.
-      // - Guarantees: see 'candidate' and 'active'.
-      // - Expected behavior: saving indicator, in-place editor is saving field
-      //   data into PrivateTempStore. Upon successful saving (without
-      //   validation errors), the in-place editor transitions the field's state
-      //   to 'saved', but to 'invalid' upon failed saving (with validation
-      //   errors).
-      'saving',
-      // In-place editor has successfully saved the changed field.
-      // - Trigger: in-place editor.
-      // - Guarantees: see 'candidate' and 'active'.
-      // - Expected behavior: transition back to 'candidate' state because the
-      //   deed is done. Then: 1) transition to 'inactive' to allow the field
-      //   to be rerendered, 2) destroy the FieldModel (which also destroys
-      //   attached views like the EditorView), 3) replace the existing field
-      //   HTML with the existing HTML and 4) attach behaviors again so that the
-      //   field becomes available again for in-place editing.
-      'saved',
-      // In-place editor has failed to saved the changed field: there were
-      // validation errors.
-      // - Trigger: in-place editor.
-      // - Guarantees: see 'candidate' and 'active'.
-      // - Expected behavior: remain in 'invalid' state, let the user make more
-      //   changes so that he can save it again, without validation errors.
-      'invalid'
-    ],
+  }, {
+    states: ['inactive', 'candidate', 'highlighted', 'activating', 'active', 'changed', 'saving', 'saved', 'invalid'],
 
-    /**
-     * Indicates whether the 'from' state comes before the 'to' state.
-     *
-     * @param {string} from
-     *   One of {@link Drupal.quickedit.FieldModel.states}.
-     * @param {string} to
-     *   One of {@link Drupal.quickedit.FieldModel.states}.
-     *
-     * @return {bool}
-     *   Whether the 'from' state comes before the 'to' state.
-     */
-    followsStateSequence: function (from, to) {
+    followsStateSequence: function followsStateSequence(from, to) {
       return _.indexOf(this.states, from) < _.indexOf(this.states, to);
     }
 
   });
 
-  /**
-   * @constructor
-   *
-   * @augments Backbone.Collection
-   */
-  Drupal.quickedit.FieldCollection = Backbone.Collection.extend(/** @lends Drupal.quickedit.FieldCollection */{
-
-    /**
-     * @type {Drupal.quickedit.FieldModel}
-     */
+  Drupal.quickedit.FieldCollection = Backbone.Collection.extend({
     model: Drupal.quickedit.FieldModel
   });
-
-}(_, Backbone, Drupal));
+})(_, Backbone, Drupal);
\ No newline at end of file
diff --git a/core/modules/quickedit/js/quickedit.es6.js b/core/modules/quickedit/js/quickedit.es6.js
new file mode 100644
index 000000000000..40bdd3e60905
--- /dev/null
+++ b/core/modules/quickedit/js/quickedit.es6.js
@@ -0,0 +1,686 @@
+/**
+ * @file
+ * Attaches behavior for the Quick Edit module.
+ *
+ * Everything happens asynchronously, to allow for:
+ *   - dynamically rendered contextual links
+ *   - asynchronously retrieved (and cached) per-field in-place editing metadata
+ *   - asynchronous setup of in-place editable field and "Quick edit" link.
+ *
+ * To achieve this, there are several queues:
+ *   - fieldsMetadataQueue: fields whose metadata still needs to be fetched.
+ *   - fieldsAvailableQueue: queue of fields whose metadata is known, and for
+ *     which it has been confirmed that the user has permission to edit them.
+ *     However, FieldModels will only be created for them once there's a
+ *     contextual link for their entity: when it's possible to initiate editing.
+ *   - contextualLinksQueue: queue of contextual links on entities for which it
+ *     is not yet known whether the user has permission to edit at >=1 of them.
+ */
+
+(function ($, _, Backbone, Drupal, drupalSettings, JSON, storage) {
+
+  'use strict';
+
+  var options = $.extend(drupalSettings.quickedit,
+    // Merge strings on top of drupalSettings so that they are not mutable.
+    {
+      strings: {
+        quickEdit: Drupal.t('Quick edit')
+      }
+    }
+  );
+
+  /**
+   * Tracks fields without metadata. Contains objects with the following keys:
+   *   - DOM el
+   *   - String fieldID
+   *   - String entityID
+   */
+  var fieldsMetadataQueue = [];
+
+  /**
+   * Tracks fields ready for use. Contains objects with the following keys:
+   *   - DOM el
+   *   - String fieldID
+   *   - String entityID
+   */
+  var fieldsAvailableQueue = [];
+
+  /**
+   * Tracks contextual links on entities. Contains objects with the following
+   * keys:
+   *   - String entityID
+   *   - DOM el
+   *   - DOM region
+   */
+  var contextualLinksQueue = [];
+
+  /**
+   * Tracks how many instances exist for each unique entity. Contains key-value
+   * pairs:
+   * - String entityID
+   * - Number count
+   */
+  var entityInstancesTracker = {};
+
+  /**
+   *
+   * @type {Drupal~behavior}
+   */
+  Drupal.behaviors.quickedit = {
+    attach: function (context) {
+      // Initialize the Quick Edit app once per page load.
+      $('body').once('quickedit-init').each(initQuickEdit);
+
+      // Find all in-place editable fields, if any.
+      var $fields = $(context).find('[data-quickedit-field-id]').once('quickedit');
+      if ($fields.length === 0) {
+        return;
+      }
+
+      // Process each entity element: identical entities that appear multiple
+      // times will get a numeric identifier, starting at 0.
+      $(context).find('[data-quickedit-entity-id]').once('quickedit').each(function (index, entityElement) {
+        processEntity(entityElement);
+      });
+
+      // Process each field element: queue to be used or to fetch metadata.
+      // When a field is being rerendered after editing, it will be processed
+      // immediately. New fields will be unable to be processed immediately,
+      // but will instead be queued to have their metadata fetched, which occurs
+      // below in fetchMissingMetaData().
+      $fields.each(function (index, fieldElement) {
+        processField(fieldElement);
+      });
+
+      // Entities and fields on the page have been detected, try to set up the
+      // contextual links for those entities that already have the necessary
+      // meta- data in the client-side cache.
+      contextualLinksQueue = _.filter(contextualLinksQueue, function (contextualLink) {
+        return !initializeEntityContextualLink(contextualLink);
+      });
+
+      // Fetch metadata for any fields that are queued to retrieve it.
+      fetchMissingMetadata(function (fieldElementsWithFreshMetadata) {
+        // Metadata has been fetched, reprocess fields whose metadata was
+        // missing.
+        _.each(fieldElementsWithFreshMetadata, processField);
+
+        // Metadata has been fetched, try to set up more contextual links now.
+        contextualLinksQueue = _.filter(contextualLinksQueue, function (contextualLink) {
+          return !initializeEntityContextualLink(contextualLink);
+        });
+      });
+    },
+    detach: function (context, settings, trigger) {
+      if (trigger === 'unload') {
+        deleteContainedModelsAndQueues($(context));
+      }
+    }
+  };
+
+  /**
+   *
+   * @namespace
+   */
+  Drupal.quickedit = {
+
+    /**
+     * A {@link Drupal.quickedit.AppView} instance.
+     */
+    app: null,
+
+    /**
+     * @type {object}
+     *
+     * @prop {Array.<Drupal.quickedit.EntityModel>} entities
+     * @prop {Array.<Drupal.quickedit.FieldModel>} fields
+     */
+    collections: {
+      // All in-place editable entities (Drupal.quickedit.EntityModel) on the
+      // page.
+      entities: null,
+      // All in-place editable fields (Drupal.quickedit.FieldModel) on the page.
+      fields: null
+    },
+
+    /**
+     * In-place editors will register themselves in this object.
+     *
+     * @namespace
+     */
+    editors: {},
+
+    /**
+     * Per-field metadata that indicates whether in-place editing is allowed,
+     * which in-place editor should be used, etc.
+     *
+     * @namespace
+     */
+    metadata: {
+
+      /**
+       * Check if a field exists in storage.
+       *
+       * @param {string} fieldID
+       *   The field id to check.
+       *
+       * @return {bool}
+       *   Whether it was found or not.
+       */
+      has: function (fieldID) {
+        return storage.getItem(this._prefixFieldID(fieldID)) !== null;
+      },
+
+      /**
+       * Add metadata to a field id.
+       *
+       * @param {string} fieldID
+       *   The field ID to add data to.
+       * @param {object} metadata
+       *   Metadata to add.
+       */
+      add: function (fieldID, metadata) {
+        storage.setItem(this._prefixFieldID(fieldID), JSON.stringify(metadata));
+      },
+
+      /**
+       * Get a key from a field id.
+       *
+       * @param {string} fieldID
+       *   The field ID to check.
+       * @param {string} [key]
+       *   The key to check. If empty, will return all metadata.
+       *
+       * @return {object|*}
+       *   The value for the key, if defined. Otherwise will return all metadata
+       *   for the specified field id.
+       *
+       */
+      get: function (fieldID, key) {
+        var metadata = JSON.parse(storage.getItem(this._prefixFieldID(fieldID)));
+        return (typeof key === 'undefined') ? metadata : metadata[key];
+      },
+
+      /**
+       * Prefix the field id.
+       *
+       * @param {string} fieldID
+       *   The field id to prefix.
+       *
+       * @return {string}
+       *   A prefixed field id.
+       */
+      _prefixFieldID: function (fieldID) {
+        return 'Drupal.quickedit.metadata.' + fieldID;
+      },
+
+      /**
+       * Unprefix the field id.
+       *
+       * @param {string} fieldID
+       *   The field id to unprefix.
+       *
+       * @return {string}
+       *   An unprefixed field id.
+       */
+      _unprefixFieldID: function (fieldID) {
+        // Strip "Drupal.quickedit.metadata.", which is 26 characters long.
+        return fieldID.substring(26);
+      },
+
+      /**
+       * Intersection calculation.
+       *
+       * @param {Array} fieldIDs
+       *   An array of field ids to compare to prefix field id.
+       *
+       * @return {Array}
+       *   The intersection found.
+       */
+      intersection: function (fieldIDs) {
+        var prefixedFieldIDs = _.map(fieldIDs, this._prefixFieldID);
+        var intersection = _.intersection(prefixedFieldIDs, _.keys(sessionStorage));
+        return _.map(intersection, this._unprefixFieldID);
+      }
+    }
+  };
+
+  // Clear the Quick Edit metadata cache whenever the current user's set of
+  // permissions changes.
+  var permissionsHashKey = Drupal.quickedit.metadata._prefixFieldID('permissionsHash');
+  var permissionsHashValue = storage.getItem(permissionsHashKey);
+  var permissionsHash = drupalSettings.user.permissionsHash;
+  if (permissionsHashValue !== permissionsHash) {
+    if (typeof permissionsHash === 'string') {
+      _.chain(storage).keys().each(function (key) {
+        if (key.substring(0, 26) === 'Drupal.quickedit.metadata.') {
+          storage.removeItem(key);
+        }
+      });
+    }
+    storage.setItem(permissionsHashKey, permissionsHash);
+  }
+
+  /**
+   * Detect contextual links on entities annotated by quickedit.
+   *
+   * Queue contextual links to be processed.
+   *
+   * @param {jQuery.Event} event
+   *   The `drupalContextualLinkAdded` event.
+   * @param {object} data
+   *   An object containing the data relevant to the event.
+   *
+   * @listens event:drupalContextualLinkAdded
+   */
+  $(document).on('drupalContextualLinkAdded', function (event, data) {
+    if (data.$region.is('[data-quickedit-entity-id]')) {
+      // If the contextual link is cached on the client side, an entity instance
+      // will not yet have been assigned. So assign one.
+      if (!data.$region.is('[data-quickedit-entity-instance-id]')) {
+        data.$region.once('quickedit');
+        processEntity(data.$region.get(0));
+      }
+      var contextualLink = {
+        entityID: data.$region.attr('data-quickedit-entity-id'),
+        entityInstanceID: data.$region.attr('data-quickedit-entity-instance-id'),
+        el: data.$el[0],
+        region: data.$region[0]
+      };
+      // Set up contextual links for this, otherwise queue it to be set up
+      // later.
+      if (!initializeEntityContextualLink(contextualLink)) {
+        contextualLinksQueue.push(contextualLink);
+      }
+    }
+  });
+
+  /**
+   * Extracts the entity ID from a field ID.
+   *
+   * @param {string} fieldID
+   *   A field ID: a string of the format
+   *   `<entity type>/<id>/<field name>/<language>/<view mode>`.
+   *
+   * @return {string}
+   *   An entity ID: a string of the format `<entity type>/<id>`.
+   */
+  function extractEntityID(fieldID) {
+    return fieldID.split('/').slice(0, 2).join('/');
+  }
+
+  /**
+   * Initialize the Quick Edit app.
+   *
+   * @param {HTMLElement} bodyElement
+   *   This document's body element.
+   */
+  function initQuickEdit(bodyElement) {
+    Drupal.quickedit.collections.entities = new Drupal.quickedit.EntityCollection();
+    Drupal.quickedit.collections.fields = new Drupal.quickedit.FieldCollection();
+
+    // Instantiate AppModel (application state) and AppView, which is the
+    // controller of the whole in-place editing experience.
+    Drupal.quickedit.app = new Drupal.quickedit.AppView({
+      el: bodyElement,
+      model: new Drupal.quickedit.AppModel(),
+      entitiesCollection: Drupal.quickedit.collections.entities,
+      fieldsCollection: Drupal.quickedit.collections.fields
+    });
+  }
+
+  /**
+   * Assigns the entity an instance ID.
+   *
+   * @param {HTMLElement} entityElement
+   *   A Drupal Entity API entity's DOM element with a data-quickedit-entity-id
+   *   attribute.
+   */
+  function processEntity(entityElement) {
+    var entityID = entityElement.getAttribute('data-quickedit-entity-id');
+    if (!entityInstancesTracker.hasOwnProperty(entityID)) {
+      entityInstancesTracker[entityID] = 0;
+    }
+    else {
+      entityInstancesTracker[entityID]++;
+    }
+
+    // Set the calculated entity instance ID for this element.
+    var entityInstanceID = entityInstancesTracker[entityID];
+    entityElement.setAttribute('data-quickedit-entity-instance-id', entityInstanceID);
+  }
+
+  /**
+   * Fetch the field's metadata; queue or initialize it (if EntityModel exists).
+   *
+   * @param {HTMLElement} fieldElement
+   *   A Drupal Field API field's DOM element with a data-quickedit-field-id
+   *   attribute.
+   */
+  function processField(fieldElement) {
+    var metadata = Drupal.quickedit.metadata;
+    var fieldID = fieldElement.getAttribute('data-quickedit-field-id');
+    var entityID = extractEntityID(fieldID);
+    // Figure out the instance ID by looking at the ancestor
+    // [data-quickedit-entity-id] element's data-quickedit-entity-instance-id
+    // attribute.
+    var entityElementSelector = '[data-quickedit-entity-id="' + entityID + '"]';
+    var entityElement = $(fieldElement).closest(entityElementSelector);
+    // In the case of a full entity view page, the entity title is rendered
+    // outside of "the entity DOM node": it's rendered as the page title. So in
+    // this case, we find the lowest common parent element (deepest in the tree)
+    // and consider that the entity element.
+    if (entityElement.length === 0) {
+      var $lowestCommonParent = $(entityElementSelector).parents().has(fieldElement).first();
+      entityElement = $lowestCommonParent.find(entityElementSelector);
+    }
+    var entityInstanceID = entityElement
+      .get(0)
+      .getAttribute('data-quickedit-entity-instance-id');
+
+    // Early-return if metadata for this field is missing.
+    if (!metadata.has(fieldID)) {
+      fieldsMetadataQueue.push({
+        el: fieldElement,
+        fieldID: fieldID,
+        entityID: entityID,
+        entityInstanceID: entityInstanceID
+      });
+      return;
+    }
+    // Early-return if the user is not allowed to in-place edit this field.
+    if (metadata.get(fieldID, 'access') !== true) {
+      return;
+    }
+
+    // If an EntityModel for this field already exists (and hence also a "Quick
+    // edit" contextual link), then initialize it immediately.
+    if (Drupal.quickedit.collections.entities.findWhere({entityID: entityID, entityInstanceID: entityInstanceID})) {
+      initializeField(fieldElement, fieldID, entityID, entityInstanceID);
+    }
+    // Otherwise: queue the field. It is now available to be set up when its
+    // corresponding entity becomes in-place editable.
+    else {
+      fieldsAvailableQueue.push({el: fieldElement, fieldID: fieldID, entityID: entityID, entityInstanceID: entityInstanceID});
+    }
+  }
+
+  /**
+   * Initialize a field; create FieldModel.
+   *
+   * @param {HTMLElement} fieldElement
+   *   The field's DOM element.
+   * @param {string} fieldID
+   *   The field's ID.
+   * @param {string} entityID
+   *   The field's entity's ID.
+   * @param {string} entityInstanceID
+   *   The field's entity's instance ID.
+   */
+  function initializeField(fieldElement, fieldID, entityID, entityInstanceID) {
+    var entity = Drupal.quickedit.collections.entities.findWhere({
+      entityID: entityID,
+      entityInstanceID: entityInstanceID
+    });
+
+    $(fieldElement).addClass('quickedit-field');
+
+    // The FieldModel stores the state of an in-place editable entity field.
+    var field = new Drupal.quickedit.FieldModel({
+      el: fieldElement,
+      fieldID: fieldID,
+      id: fieldID + '[' + entity.get('entityInstanceID') + ']',
+      entity: entity,
+      metadata: Drupal.quickedit.metadata.get(fieldID),
+      acceptStateChange: _.bind(Drupal.quickedit.app.acceptEditorStateChange, Drupal.quickedit.app)
+    });
+
+    // Track all fields on the page.
+    Drupal.quickedit.collections.fields.add(field);
+  }
+
+  /**
+   * Fetches metadata for fields whose metadata is missing.
+   *
+   * Fields whose metadata is missing are tracked at fieldsMetadataQueue.
+   *
+   * @param {function} callback
+   *   A callback function that receives field elements whose metadata will just
+   *   have been fetched.
+   */
+  function fetchMissingMetadata(callback) {
+    if (fieldsMetadataQueue.length) {
+      var fieldIDs = _.pluck(fieldsMetadataQueue, 'fieldID');
+      var fieldElementsWithoutMetadata = _.pluck(fieldsMetadataQueue, 'el');
+      var entityIDs = _.uniq(_.pluck(fieldsMetadataQueue, 'entityID'), true);
+      // Ensure we only request entityIDs for which we don't have metadata yet.
+      entityIDs = _.difference(entityIDs, Drupal.quickedit.metadata.intersection(entityIDs));
+      fieldsMetadataQueue = [];
+
+      $.ajax({
+        url: Drupal.url('quickedit/metadata'),
+        type: 'POST',
+        data: {
+          'fields[]': fieldIDs,
+          'entities[]': entityIDs
+        },
+        dataType: 'json',
+        success: function (results) {
+          // Store the metadata.
+          _.each(results, function (fieldMetadata, fieldID) {
+            Drupal.quickedit.metadata.add(fieldID, fieldMetadata);
+          });
+
+          callback(fieldElementsWithoutMetadata);
+        }
+      });
+    }
+  }
+
+  /**
+   * Loads missing in-place editor's attachments (JavaScript and CSS files).
+   *
+   * Missing in-place editors are those whose fields are actively being used on
+   * the page but don't have.
+   *
+   * @param {function} callback
+   *   Callback function to be called when the missing in-place editors (if any)
+   *   have been inserted into the DOM. i.e. they may still be loading.
+   */
+  function loadMissingEditors(callback) {
+    var loadedEditors = _.keys(Drupal.quickedit.editors);
+    var missingEditors = [];
+    Drupal.quickedit.collections.fields.each(function (fieldModel) {
+      var metadata = Drupal.quickedit.metadata.get(fieldModel.get('fieldID'));
+      if (metadata.access && _.indexOf(loadedEditors, metadata.editor) === -1) {
+        missingEditors.push(metadata.editor);
+        // Set a stub, to prevent subsequent calls to loadMissingEditors() from
+        // loading the same in-place editor again. Loading an in-place editor
+        // requires talking to a server, to download its JavaScript, then
+        // executing its JavaScript, and only then its Drupal.quickedit.editors
+        // entry will be set.
+        Drupal.quickedit.editors[metadata.editor] = false;
+      }
+    });
+    missingEditors = _.uniq(missingEditors);
+    if (missingEditors.length === 0) {
+      callback();
+      return;
+    }
+
+    // @see https://www.drupal.org/node/2029999.
+    // Create a Drupal.Ajax instance to load the form.
+    var loadEditorsAjax = Drupal.ajax({
+      url: Drupal.url('quickedit/attachments'),
+      submit: {'editors[]': missingEditors}
+    });
+    // Implement a scoped insert AJAX command: calls the callback after all AJAX
+    // command functions have been executed (hence the deferred calling).
+    var realInsert = Drupal.AjaxCommands.prototype.insert;
+    loadEditorsAjax.commands.insert = function (ajax, response, status) {
+      _.defer(callback);
+      realInsert(ajax, response, status);
+    };
+    // Trigger the AJAX request, which will should return AJAX commands to
+    // insert any missing attachments.
+    loadEditorsAjax.execute();
+  }
+
+  /**
+   * Attempts to set up a "Quick edit" link and corresponding EntityModel.
+   *
+   * @param {object} contextualLink
+   *   An object with the following properties:
+   *     - String entityID: a Quick Edit entity identifier, e.g. "node/1" or
+   *       "block_content/5".
+   *     - String entityInstanceID: a Quick Edit entity instance identifier,
+   *       e.g. 0, 1 or n (depending on whether it's the first, second, or n+1st
+   *       instance of this entity).
+   *     - DOM el: element pointing to the contextual links placeholder for this
+   *       entity.
+   *     - DOM region: element pointing to the contextual region of this entity.
+   *
+   * @return {bool}
+   *   Returns true when a contextual the given contextual link metadata can be
+   *   removed from the queue (either because the contextual link has been set
+   *   up or because it is certain that in-place editing is not allowed for any
+   *   of its fields). Returns false otherwise.
+   */
+  function initializeEntityContextualLink(contextualLink) {
+    var metadata = Drupal.quickedit.metadata;
+    // Check if the user has permission to edit at least one of them.
+    function hasFieldWithPermission(fieldIDs) {
+      for (var i = 0; i < fieldIDs.length; i++) {
+        var fieldID = fieldIDs[i];
+        if (metadata.get(fieldID, 'access') === true) {
+          return true;
+        }
+      }
+      return false;
+    }
+
+    // Checks if the metadata for all given field IDs exists.
+    function allMetadataExists(fieldIDs) {
+      return fieldIDs.length === metadata.intersection(fieldIDs).length;
+    }
+
+    // Find all fields for this entity instance and collect their field IDs.
+    var fields = _.where(fieldsAvailableQueue, {
+      entityID: contextualLink.entityID,
+      entityInstanceID: contextualLink.entityInstanceID
+    });
+    var fieldIDs = _.pluck(fields, 'fieldID');
+
+    // No fields found yet.
+    if (fieldIDs.length === 0) {
+      return false;
+    }
+    // The entity for the given contextual link contains at least one field that
+    // the current user may edit in-place; instantiate EntityModel,
+    // EntityDecorationView and ContextualLinkView.
+    else if (hasFieldWithPermission(fieldIDs)) {
+      var entityModel = new Drupal.quickedit.EntityModel({
+        el: contextualLink.region,
+        entityID: contextualLink.entityID,
+        entityInstanceID: contextualLink.entityInstanceID,
+        id: contextualLink.entityID + '[' + contextualLink.entityInstanceID + ']',
+        label: Drupal.quickedit.metadata.get(contextualLink.entityID, 'label')
+      });
+      Drupal.quickedit.collections.entities.add(entityModel);
+      // Create an EntityDecorationView associated with the root DOM node of the
+      // entity.
+      var entityDecorationView = new Drupal.quickedit.EntityDecorationView({
+        el: contextualLink.region,
+        model: entityModel
+      });
+      entityModel.set('entityDecorationView', entityDecorationView);
+
+      // Initialize all queued fields within this entity (creates FieldModels).
+      _.each(fields, function (field) {
+        initializeField(field.el, field.fieldID, contextualLink.entityID, contextualLink.entityInstanceID);
+      });
+      fieldsAvailableQueue = _.difference(fieldsAvailableQueue, fields);
+
+      // Initialization should only be called once. Use Underscore's once method
+      // to get a one-time use version of the function.
+      var initContextualLink = _.once(function () {
+        var $links = $(contextualLink.el).find('.contextual-links');
+        var contextualLinkView = new Drupal.quickedit.ContextualLinkView($.extend({
+          el: $('<li class="quickedit"><a href="" role="button" aria-pressed="false"></a></li>').prependTo($links),
+          model: entityModel,
+          appModel: Drupal.quickedit.app.model
+        }, options));
+        entityModel.set('contextualLinkView', contextualLinkView);
+      });
+
+      // Set up ContextualLinkView after loading any missing in-place editors.
+      loadMissingEditors(initContextualLink);
+
+      return true;
+    }
+    // There was not at least one field that the current user may edit in-place,
+    // even though the metadata for all fields within this entity is available.
+    else if (allMetadataExists(fieldIDs)) {
+      return true;
+    }
+
+    return false;
+  }
+
+  /**
+   * Delete models and queue items that are contained within a given context.
+   *
+   * Deletes any contained EntityModels (plus their associated FieldModels and
+   * ContextualLinkView) and FieldModels, as well as the corresponding queues.
+   *
+   * After EntityModels, FieldModels must also be deleted, because it is
+   * possible in Drupal for a field DOM element to exist outside of the entity
+   * DOM element, e.g. when viewing the full node, the title of the node is not
+   * rendered within the node (the entity) but as the page title.
+   *
+   * Note: this will not delete an entity that is actively being in-place
+   * edited.
+   *
+   * @param {jQuery} $context
+   *   The context within which to delete.
+   */
+  function deleteContainedModelsAndQueues($context) {
+    $context.find('[data-quickedit-entity-id]').addBack('[data-quickedit-entity-id]').each(function (index, entityElement) {
+      // Delete entity model.
+      var entityModel = Drupal.quickedit.collections.entities.findWhere({el: entityElement});
+      if (entityModel) {
+        var contextualLinkView = entityModel.get('contextualLinkView');
+        contextualLinkView.undelegateEvents();
+        contextualLinkView.remove();
+        // Remove the EntityDecorationView.
+        entityModel.get('entityDecorationView').remove();
+        // Destroy the EntityModel; this will also destroy its FieldModels.
+        entityModel.destroy();
+      }
+
+      // Filter queue.
+      function hasOtherRegion(contextualLink) {
+        return contextualLink.region !== entityElement;
+      }
+
+      contextualLinksQueue = _.filter(contextualLinksQueue, hasOtherRegion);
+    });
+
+    $context.find('[data-quickedit-field-id]').addBack('[data-quickedit-field-id]').each(function (index, fieldElement) {
+      // Delete field models.
+      Drupal.quickedit.collections.fields.chain()
+        .filter(function (fieldModel) { return fieldModel.get('el') === fieldElement; })
+        .invoke('destroy');
+
+      // Filter queues.
+      function hasOtherFieldElement(field) {
+        return field.el !== fieldElement;
+      }
+
+      fieldsMetadataQueue = _.filter(fieldsMetadataQueue, hasOtherFieldElement);
+      fieldsAvailableQueue = _.filter(fieldsAvailableQueue, hasOtherFieldElement);
+    });
+  }
+
+})(jQuery, _, Backbone, Drupal, drupalSettings, window.JSON, window.sessionStorage);
diff --git a/core/modules/quickedit/js/quickedit.js b/core/modules/quickedit/js/quickedit.js
index 40bdd3e60905..5f888ea817f7 100644
--- a/core/modules/quickedit/js/quickedit.js
+++ b/core/modules/quickedit/js/quickedit.js
@@ -1,244 +1,99 @@
 /**
- * @file
- * Attaches behavior for the Quick Edit module.
- *
- * Everything happens asynchronously, to allow for:
- *   - dynamically rendered contextual links
- *   - asynchronously retrieved (and cached) per-field in-place editing metadata
- *   - asynchronous setup of in-place editable field and "Quick edit" link.
- *
- * To achieve this, there are several queues:
- *   - fieldsMetadataQueue: fields whose metadata still needs to be fetched.
- *   - fieldsAvailableQueue: queue of fields whose metadata is known, and for
- *     which it has been confirmed that the user has permission to edit them.
- *     However, FieldModels will only be created for them once there's a
- *     contextual link for their entity: when it's possible to initiate editing.
- *   - contextualLinksQueue: queue of contextual links on entities for which it
- *     is not yet known whether the user has permission to edit at >=1 of them.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/quickedit/js/quickedit.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, _, Backbone, Drupal, drupalSettings, JSON, storage) {
 
   'use strict';
 
-  var options = $.extend(drupalSettings.quickedit,
-    // Merge strings on top of drupalSettings so that they are not mutable.
-    {
-      strings: {
-        quickEdit: Drupal.t('Quick edit')
-      }
+  var options = $.extend(drupalSettings.quickedit, {
+    strings: {
+      quickEdit: Drupal.t('Quick edit')
     }
-  );
-
-  /**
-   * Tracks fields without metadata. Contains objects with the following keys:
-   *   - DOM el
-   *   - String fieldID
-   *   - String entityID
-   */
+  });
+
   var fieldsMetadataQueue = [];
 
-  /**
-   * Tracks fields ready for use. Contains objects with the following keys:
-   *   - DOM el
-   *   - String fieldID
-   *   - String entityID
-   */
   var fieldsAvailableQueue = [];
 
-  /**
-   * Tracks contextual links on entities. Contains objects with the following
-   * keys:
-   *   - String entityID
-   *   - DOM el
-   *   - DOM region
-   */
   var contextualLinksQueue = [];
 
-  /**
-   * Tracks how many instances exist for each unique entity. Contains key-value
-   * pairs:
-   * - String entityID
-   * - Number count
-   */
   var entityInstancesTracker = {};
 
-  /**
-   *
-   * @type {Drupal~behavior}
-   */
   Drupal.behaviors.quickedit = {
-    attach: function (context) {
-      // Initialize the Quick Edit app once per page load.
+    attach: function attach(context) {
       $('body').once('quickedit-init').each(initQuickEdit);
 
-      // Find all in-place editable fields, if any.
       var $fields = $(context).find('[data-quickedit-field-id]').once('quickedit');
       if ($fields.length === 0) {
         return;
       }
 
-      // Process each entity element: identical entities that appear multiple
-      // times will get a numeric identifier, starting at 0.
       $(context).find('[data-quickedit-entity-id]').once('quickedit').each(function (index, entityElement) {
         processEntity(entityElement);
       });
 
-      // Process each field element: queue to be used or to fetch metadata.
-      // When a field is being rerendered after editing, it will be processed
-      // immediately. New fields will be unable to be processed immediately,
-      // but will instead be queued to have their metadata fetched, which occurs
-      // below in fetchMissingMetaData().
       $fields.each(function (index, fieldElement) {
         processField(fieldElement);
       });
 
-      // Entities and fields on the page have been detected, try to set up the
-      // contextual links for those entities that already have the necessary
-      // meta- data in the client-side cache.
       contextualLinksQueue = _.filter(contextualLinksQueue, function (contextualLink) {
         return !initializeEntityContextualLink(contextualLink);
       });
 
-      // Fetch metadata for any fields that are queued to retrieve it.
       fetchMissingMetadata(function (fieldElementsWithFreshMetadata) {
-        // Metadata has been fetched, reprocess fields whose metadata was
-        // missing.
         _.each(fieldElementsWithFreshMetadata, processField);
 
-        // Metadata has been fetched, try to set up more contextual links now.
         contextualLinksQueue = _.filter(contextualLinksQueue, function (contextualLink) {
           return !initializeEntityContextualLink(contextualLink);
         });
       });
     },
-    detach: function (context, settings, trigger) {
+    detach: function detach(context, settings, trigger) {
       if (trigger === 'unload') {
         deleteContainedModelsAndQueues($(context));
       }
     }
   };
 
-  /**
-   *
-   * @namespace
-   */
   Drupal.quickedit = {
-
-    /**
-     * A {@link Drupal.quickedit.AppView} instance.
-     */
     app: null,
 
-    /**
-     * @type {object}
-     *
-     * @prop {Array.<Drupal.quickedit.EntityModel>} entities
-     * @prop {Array.<Drupal.quickedit.FieldModel>} fields
-     */
     collections: {
-      // All in-place editable entities (Drupal.quickedit.EntityModel) on the
-      // page.
       entities: null,
-      // All in-place editable fields (Drupal.quickedit.FieldModel) on the page.
+
       fields: null
     },
 
-    /**
-     * In-place editors will register themselves in this object.
-     *
-     * @namespace
-     */
     editors: {},
 
-    /**
-     * Per-field metadata that indicates whether in-place editing is allowed,
-     * which in-place editor should be used, etc.
-     *
-     * @namespace
-     */
     metadata: {
-
-      /**
-       * Check if a field exists in storage.
-       *
-       * @param {string} fieldID
-       *   The field id to check.
-       *
-       * @return {bool}
-       *   Whether it was found or not.
-       */
-      has: function (fieldID) {
+      has: function has(fieldID) {
         return storage.getItem(this._prefixFieldID(fieldID)) !== null;
       },
 
-      /**
-       * Add metadata to a field id.
-       *
-       * @param {string} fieldID
-       *   The field ID to add data to.
-       * @param {object} metadata
-       *   Metadata to add.
-       */
-      add: function (fieldID, metadata) {
+      add: function add(fieldID, metadata) {
         storage.setItem(this._prefixFieldID(fieldID), JSON.stringify(metadata));
       },
 
-      /**
-       * Get a key from a field id.
-       *
-       * @param {string} fieldID
-       *   The field ID to check.
-       * @param {string} [key]
-       *   The key to check. If empty, will return all metadata.
-       *
-       * @return {object|*}
-       *   The value for the key, if defined. Otherwise will return all metadata
-       *   for the specified field id.
-       *
-       */
-      get: function (fieldID, key) {
+      get: function get(fieldID, key) {
         var metadata = JSON.parse(storage.getItem(this._prefixFieldID(fieldID)));
-        return (typeof key === 'undefined') ? metadata : metadata[key];
+        return typeof key === 'undefined' ? metadata : metadata[key];
       },
 
-      /**
-       * Prefix the field id.
-       *
-       * @param {string} fieldID
-       *   The field id to prefix.
-       *
-       * @return {string}
-       *   A prefixed field id.
-       */
-      _prefixFieldID: function (fieldID) {
+      _prefixFieldID: function _prefixFieldID(fieldID) {
         return 'Drupal.quickedit.metadata.' + fieldID;
       },
 
-      /**
-       * Unprefix the field id.
-       *
-       * @param {string} fieldID
-       *   The field id to unprefix.
-       *
-       * @return {string}
-       *   An unprefixed field id.
-       */
-      _unprefixFieldID: function (fieldID) {
-        // Strip "Drupal.quickedit.metadata.", which is 26 characters long.
+      _unprefixFieldID: function _unprefixFieldID(fieldID) {
         return fieldID.substring(26);
       },
 
-      /**
-       * Intersection calculation.
-       *
-       * @param {Array} fieldIDs
-       *   An array of field ids to compare to prefix field id.
-       *
-       * @return {Array}
-       *   The intersection found.
-       */
-      intersection: function (fieldIDs) {
+      intersection: function intersection(fieldIDs) {
         var prefixedFieldIDs = _.map(fieldIDs, this._prefixFieldID);
         var intersection = _.intersection(prefixedFieldIDs, _.keys(sessionStorage));
         return _.map(intersection, this._unprefixFieldID);
@@ -246,8 +101,6 @@
     }
   };
 
-  // Clear the Quick Edit metadata cache whenever the current user's set of
-  // permissions changes.
   var permissionsHashKey = Drupal.quickedit.metadata._prefixFieldID('permissionsHash');
   var permissionsHashValue = storage.getItem(permissionsHashKey);
   var permissionsHash = drupalSettings.user.permissionsHash;
@@ -262,22 +115,8 @@
     storage.setItem(permissionsHashKey, permissionsHash);
   }
 
-  /**
-   * Detect contextual links on entities annotated by quickedit.
-   *
-   * Queue contextual links to be processed.
-   *
-   * @param {jQuery.Event} event
-   *   The `drupalContextualLinkAdded` event.
-   * @param {object} data
-   *   An object containing the data relevant to the event.
-   *
-   * @listens event:drupalContextualLinkAdded
-   */
   $(document).on('drupalContextualLinkAdded', function (event, data) {
     if (data.$region.is('[data-quickedit-entity-id]')) {
-      // If the contextual link is cached on the client side, an entity instance
-      // will not yet have been assigned. So assign one.
       if (!data.$region.is('[data-quickedit-entity-instance-id]')) {
         data.$region.once('quickedit');
         processEntity(data.$region.get(0));
@@ -288,40 +127,21 @@
         el: data.$el[0],
         region: data.$region[0]
       };
-      // Set up contextual links for this, otherwise queue it to be set up
-      // later.
+
       if (!initializeEntityContextualLink(contextualLink)) {
         contextualLinksQueue.push(contextualLink);
       }
     }
   });
 
-  /**
-   * Extracts the entity ID from a field ID.
-   *
-   * @param {string} fieldID
-   *   A field ID: a string of the format
-   *   `<entity type>/<id>/<field name>/<language>/<view mode>`.
-   *
-   * @return {string}
-   *   An entity ID: a string of the format `<entity type>/<id>`.
-   */
   function extractEntityID(fieldID) {
     return fieldID.split('/').slice(0, 2).join('/');
   }
 
-  /**
-   * Initialize the Quick Edit app.
-   *
-   * @param {HTMLElement} bodyElement
-   *   This document's body element.
-   */
   function initQuickEdit(bodyElement) {
     Drupal.quickedit.collections.entities = new Drupal.quickedit.EntityCollection();
     Drupal.quickedit.collections.fields = new Drupal.quickedit.FieldCollection();
 
-    // Instantiate AppModel (application state) and AppView, which is the
-    // controller of the whole in-place editing experience.
     Drupal.quickedit.app = new Drupal.quickedit.AppView({
       el: bodyElement,
       model: new Drupal.quickedit.AppModel(),
@@ -330,56 +150,32 @@
     });
   }
 
-  /**
-   * Assigns the entity an instance ID.
-   *
-   * @param {HTMLElement} entityElement
-   *   A Drupal Entity API entity's DOM element with a data-quickedit-entity-id
-   *   attribute.
-   */
   function processEntity(entityElement) {
     var entityID = entityElement.getAttribute('data-quickedit-entity-id');
     if (!entityInstancesTracker.hasOwnProperty(entityID)) {
       entityInstancesTracker[entityID] = 0;
-    }
-    else {
+    } else {
       entityInstancesTracker[entityID]++;
     }
 
-    // Set the calculated entity instance ID for this element.
     var entityInstanceID = entityInstancesTracker[entityID];
     entityElement.setAttribute('data-quickedit-entity-instance-id', entityInstanceID);
   }
 
-  /**
-   * Fetch the field's metadata; queue or initialize it (if EntityModel exists).
-   *
-   * @param {HTMLElement} fieldElement
-   *   A Drupal Field API field's DOM element with a data-quickedit-field-id
-   *   attribute.
-   */
   function processField(fieldElement) {
     var metadata = Drupal.quickedit.metadata;
     var fieldID = fieldElement.getAttribute('data-quickedit-field-id');
     var entityID = extractEntityID(fieldID);
-    // Figure out the instance ID by looking at the ancestor
-    // [data-quickedit-entity-id] element's data-quickedit-entity-instance-id
-    // attribute.
+
     var entityElementSelector = '[data-quickedit-entity-id="' + entityID + '"]';
     var entityElement = $(fieldElement).closest(entityElementSelector);
-    // In the case of a full entity view page, the entity title is rendered
-    // outside of "the entity DOM node": it's rendered as the page title. So in
-    // this case, we find the lowest common parent element (deepest in the tree)
-    // and consider that the entity element.
+
     if (entityElement.length === 0) {
       var $lowestCommonParent = $(entityElementSelector).parents().has(fieldElement).first();
       entityElement = $lowestCommonParent.find(entityElementSelector);
     }
-    var entityInstanceID = entityElement
-      .get(0)
-      .getAttribute('data-quickedit-entity-instance-id');
+    var entityInstanceID = entityElement.get(0).getAttribute('data-quickedit-entity-instance-id');
 
-    // Early-return if metadata for this field is missing.
     if (!metadata.has(fieldID)) {
       fieldsMetadataQueue.push({
         el: fieldElement,
@@ -389,35 +185,18 @@
       });
       return;
     }
-    // Early-return if the user is not allowed to in-place edit this field.
+
     if (metadata.get(fieldID, 'access') !== true) {
       return;
     }
 
-    // If an EntityModel for this field already exists (and hence also a "Quick
-    // edit" contextual link), then initialize it immediately.
-    if (Drupal.quickedit.collections.entities.findWhere({entityID: entityID, entityInstanceID: entityInstanceID})) {
+    if (Drupal.quickedit.collections.entities.findWhere({ entityID: entityID, entityInstanceID: entityInstanceID })) {
       initializeField(fieldElement, fieldID, entityID, entityInstanceID);
-    }
-    // Otherwise: queue the field. It is now available to be set up when its
-    // corresponding entity becomes in-place editable.
-    else {
-      fieldsAvailableQueue.push({el: fieldElement, fieldID: fieldID, entityID: entityID, entityInstanceID: entityInstanceID});
-    }
+    } else {
+        fieldsAvailableQueue.push({ el: fieldElement, fieldID: fieldID, entityID: entityID, entityInstanceID: entityInstanceID });
+      }
   }
 
-  /**
-   * Initialize a field; create FieldModel.
-   *
-   * @param {HTMLElement} fieldElement
-   *   The field's DOM element.
-   * @param {string} fieldID
-   *   The field's ID.
-   * @param {string} entityID
-   *   The field's entity's ID.
-   * @param {string} entityInstanceID
-   *   The field's entity's instance ID.
-   */
   function initializeField(fieldElement, fieldID, entityID, entityInstanceID) {
     var entity = Drupal.quickedit.collections.entities.findWhere({
       entityID: entityID,
@@ -426,7 +205,6 @@
 
     $(fieldElement).addClass('quickedit-field');
 
-    // The FieldModel stores the state of an in-place editable entity field.
     var field = new Drupal.quickedit.FieldModel({
       el: fieldElement,
       fieldID: fieldID,
@@ -436,25 +214,15 @@
       acceptStateChange: _.bind(Drupal.quickedit.app.acceptEditorStateChange, Drupal.quickedit.app)
     });
 
-    // Track all fields on the page.
     Drupal.quickedit.collections.fields.add(field);
   }
 
-  /**
-   * Fetches metadata for fields whose metadata is missing.
-   *
-   * Fields whose metadata is missing are tracked at fieldsMetadataQueue.
-   *
-   * @param {function} callback
-   *   A callback function that receives field elements whose metadata will just
-   *   have been fetched.
-   */
   function fetchMissingMetadata(callback) {
     if (fieldsMetadataQueue.length) {
       var fieldIDs = _.pluck(fieldsMetadataQueue, 'fieldID');
       var fieldElementsWithoutMetadata = _.pluck(fieldsMetadataQueue, 'el');
       var entityIDs = _.uniq(_.pluck(fieldsMetadataQueue, 'entityID'), true);
-      // Ensure we only request entityIDs for which we don't have metadata yet.
+
       entityIDs = _.difference(entityIDs, Drupal.quickedit.metadata.intersection(entityIDs));
       fieldsMetadataQueue = [];
 
@@ -466,8 +234,7 @@
           'entities[]': entityIDs
         },
         dataType: 'json',
-        success: function (results) {
-          // Store the metadata.
+        success: function success(results) {
           _.each(results, function (fieldMetadata, fieldID) {
             Drupal.quickedit.metadata.add(fieldID, fieldMetadata);
           });
@@ -478,16 +245,6 @@
     }
   }
 
-  /**
-   * Loads missing in-place editor's attachments (JavaScript and CSS files).
-   *
-   * Missing in-place editors are those whose fields are actively being used on
-   * the page but don't have.
-   *
-   * @param {function} callback
-   *   Callback function to be called when the missing in-place editors (if any)
-   *   have been inserted into the DOM. i.e. they may still be loading.
-   */
   function loadMissingEditors(callback) {
     var loadedEditors = _.keys(Drupal.quickedit.editors);
     var missingEditors = [];
@@ -495,11 +252,7 @@
       var metadata = Drupal.quickedit.metadata.get(fieldModel.get('fieldID'));
       if (metadata.access && _.indexOf(loadedEditors, metadata.editor) === -1) {
         missingEditors.push(metadata.editor);
-        // Set a stub, to prevent subsequent calls to loadMissingEditors() from
-        // loading the same in-place editor again. Loading an in-place editor
-        // requires talking to a server, to download its JavaScript, then
-        // executing its JavaScript, and only then its Drupal.quickedit.editors
-        // entry will be set.
+
         Drupal.quickedit.editors[metadata.editor] = false;
       }
     });
@@ -509,47 +262,23 @@
       return;
     }
 
-    // @see https://www.drupal.org/node/2029999.
-    // Create a Drupal.Ajax instance to load the form.
     var loadEditorsAjax = Drupal.ajax({
       url: Drupal.url('quickedit/attachments'),
-      submit: {'editors[]': missingEditors}
+      submit: { 'editors[]': missingEditors }
     });
-    // Implement a scoped insert AJAX command: calls the callback after all AJAX
-    // command functions have been executed (hence the deferred calling).
+
     var realInsert = Drupal.AjaxCommands.prototype.insert;
     loadEditorsAjax.commands.insert = function (ajax, response, status) {
       _.defer(callback);
       realInsert(ajax, response, status);
     };
-    // Trigger the AJAX request, which will should return AJAX commands to
-    // insert any missing attachments.
+
     loadEditorsAjax.execute();
   }
 
-  /**
-   * Attempts to set up a "Quick edit" link and corresponding EntityModel.
-   *
-   * @param {object} contextualLink
-   *   An object with the following properties:
-   *     - String entityID: a Quick Edit entity identifier, e.g. "node/1" or
-   *       "block_content/5".
-   *     - String entityInstanceID: a Quick Edit entity instance identifier,
-   *       e.g. 0, 1 or n (depending on whether it's the first, second, or n+1st
-   *       instance of this entity).
-   *     - DOM el: element pointing to the contextual links placeholder for this
-   *       entity.
-   *     - DOM region: element pointing to the contextual region of this entity.
-   *
-   * @return {bool}
-   *   Returns true when a contextual the given contextual link metadata can be
-   *   removed from the queue (either because the contextual link has been set
-   *   up or because it is certain that in-place editing is not allowed for any
-   *   of its fields). Returns false otherwise.
-   */
   function initializeEntityContextualLink(contextualLink) {
     var metadata = Drupal.quickedit.metadata;
-    // Check if the user has permission to edit at least one of them.
+
     function hasFieldWithPermission(fieldIDs) {
       for (var i = 0; i < fieldIDs.length; i++) {
         var fieldID = fieldIDs[i];
@@ -560,106 +289,72 @@
       return false;
     }
 
-    // Checks if the metadata for all given field IDs exists.
     function allMetadataExists(fieldIDs) {
       return fieldIDs.length === metadata.intersection(fieldIDs).length;
     }
 
-    // Find all fields for this entity instance and collect their field IDs.
     var fields = _.where(fieldsAvailableQueue, {
       entityID: contextualLink.entityID,
       entityInstanceID: contextualLink.entityInstanceID
     });
     var fieldIDs = _.pluck(fields, 'fieldID');
 
-    // No fields found yet.
     if (fieldIDs.length === 0) {
       return false;
-    }
-    // The entity for the given contextual link contains at least one field that
-    // the current user may edit in-place; instantiate EntityModel,
-    // EntityDecorationView and ContextualLinkView.
-    else if (hasFieldWithPermission(fieldIDs)) {
-      var entityModel = new Drupal.quickedit.EntityModel({
-        el: contextualLink.region,
-        entityID: contextualLink.entityID,
-        entityInstanceID: contextualLink.entityInstanceID,
-        id: contextualLink.entityID + '[' + contextualLink.entityInstanceID + ']',
-        label: Drupal.quickedit.metadata.get(contextualLink.entityID, 'label')
-      });
-      Drupal.quickedit.collections.entities.add(entityModel);
-      // Create an EntityDecorationView associated with the root DOM node of the
-      // entity.
-      var entityDecorationView = new Drupal.quickedit.EntityDecorationView({
-        el: contextualLink.region,
-        model: entityModel
-      });
-      entityModel.set('entityDecorationView', entityDecorationView);
+    } else if (hasFieldWithPermission(fieldIDs)) {
+        var entityModel = new Drupal.quickedit.EntityModel({
+          el: contextualLink.region,
+          entityID: contextualLink.entityID,
+          entityInstanceID: contextualLink.entityInstanceID,
+          id: contextualLink.entityID + '[' + contextualLink.entityInstanceID + ']',
+          label: Drupal.quickedit.metadata.get(contextualLink.entityID, 'label')
+        });
+        Drupal.quickedit.collections.entities.add(entityModel);
 
-      // Initialize all queued fields within this entity (creates FieldModels).
-      _.each(fields, function (field) {
-        initializeField(field.el, field.fieldID, contextualLink.entityID, contextualLink.entityInstanceID);
-      });
-      fieldsAvailableQueue = _.difference(fieldsAvailableQueue, fields);
-
-      // Initialization should only be called once. Use Underscore's once method
-      // to get a one-time use version of the function.
-      var initContextualLink = _.once(function () {
-        var $links = $(contextualLink.el).find('.contextual-links');
-        var contextualLinkView = new Drupal.quickedit.ContextualLinkView($.extend({
-          el: $('<li class="quickedit"><a href="" role="button" aria-pressed="false"></a></li>').prependTo($links),
-          model: entityModel,
-          appModel: Drupal.quickedit.app.model
-        }, options));
-        entityModel.set('contextualLinkView', contextualLinkView);
-      });
+        var entityDecorationView = new Drupal.quickedit.EntityDecorationView({
+          el: contextualLink.region,
+          model: entityModel
+        });
+        entityModel.set('entityDecorationView', entityDecorationView);
+
+        _.each(fields, function (field) {
+          initializeField(field.el, field.fieldID, contextualLink.entityID, contextualLink.entityInstanceID);
+        });
+        fieldsAvailableQueue = _.difference(fieldsAvailableQueue, fields);
+
+        var initContextualLink = _.once(function () {
+          var $links = $(contextualLink.el).find('.contextual-links');
+          var contextualLinkView = new Drupal.quickedit.ContextualLinkView($.extend({
+            el: $('<li class="quickedit"><a href="" role="button" aria-pressed="false"></a></li>').prependTo($links),
+            model: entityModel,
+            appModel: Drupal.quickedit.app.model
+          }, options));
+          entityModel.set('contextualLinkView', contextualLinkView);
+        });
 
-      // Set up ContextualLinkView after loading any missing in-place editors.
-      loadMissingEditors(initContextualLink);
+        loadMissingEditors(initContextualLink);
 
-      return true;
-    }
-    // There was not at least one field that the current user may edit in-place,
-    // even though the metadata for all fields within this entity is available.
-    else if (allMetadataExists(fieldIDs)) {
-      return true;
-    }
+        return true;
+      } else if (allMetadataExists(fieldIDs)) {
+          return true;
+        }
 
     return false;
   }
 
-  /**
-   * Delete models and queue items that are contained within a given context.
-   *
-   * Deletes any contained EntityModels (plus their associated FieldModels and
-   * ContextualLinkView) and FieldModels, as well as the corresponding queues.
-   *
-   * After EntityModels, FieldModels must also be deleted, because it is
-   * possible in Drupal for a field DOM element to exist outside of the entity
-   * DOM element, e.g. when viewing the full node, the title of the node is not
-   * rendered within the node (the entity) but as the page title.
-   *
-   * Note: this will not delete an entity that is actively being in-place
-   * edited.
-   *
-   * @param {jQuery} $context
-   *   The context within which to delete.
-   */
   function deleteContainedModelsAndQueues($context) {
     $context.find('[data-quickedit-entity-id]').addBack('[data-quickedit-entity-id]').each(function (index, entityElement) {
-      // Delete entity model.
-      var entityModel = Drupal.quickedit.collections.entities.findWhere({el: entityElement});
+      var entityModel = Drupal.quickedit.collections.entities.findWhere({ el: entityElement });
       if (entityModel) {
         var contextualLinkView = entityModel.get('contextualLinkView');
         contextualLinkView.undelegateEvents();
         contextualLinkView.remove();
-        // Remove the EntityDecorationView.
+
         entityModel.get('entityDecorationView').remove();
-        // Destroy the EntityModel; this will also destroy its FieldModels.
+
         entityModel.destroy();
       }
 
-      // Filter queue.
       function hasOtherRegion(contextualLink) {
         return contextualLink.region !== entityElement;
       }
@@ -668,12 +363,10 @@
     });
 
     $context.find('[data-quickedit-field-id]').addBack('[data-quickedit-field-id]').each(function (index, fieldElement) {
-      // Delete field models.
-      Drupal.quickedit.collections.fields.chain()
-        .filter(function (fieldModel) { return fieldModel.get('el') === fieldElement; })
-        .invoke('destroy');
+      Drupal.quickedit.collections.fields.chain().filter(function (fieldModel) {
+        return fieldModel.get('el') === fieldElement;
+      }).invoke('destroy');
 
-      // Filter queues.
       function hasOtherFieldElement(field) {
         return field.el !== fieldElement;
       }
@@ -682,5 +375,4 @@
       fieldsAvailableQueue = _.filter(fieldsAvailableQueue, hasOtherFieldElement);
     });
   }
-
-})(jQuery, _, Backbone, Drupal, drupalSettings, window.JSON, window.sessionStorage);
+})(jQuery, _, Backbone, Drupal, drupalSettings, window.JSON, window.sessionStorage);
\ No newline at end of file
diff --git a/core/modules/quickedit/js/theme.es6.js b/core/modules/quickedit/js/theme.es6.js
new file mode 100644
index 000000000000..93dc3f238df2
--- /dev/null
+++ b/core/modules/quickedit/js/theme.es6.js
@@ -0,0 +1,187 @@
+/**
+ * @file
+ * Provides theme functions for all of Quick Edit's client-side HTML.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * Theme function for a "backstage" for the Quick Edit module.
+   *
+   * @param {object} settings
+   *   Settings object used to construct the markup.
+   * @param {string} settings.id
+   *   The id to apply to the backstage.
+   *
+   * @return {string}
+   *   The corresponding HTML.
+   */
+  Drupal.theme.quickeditBackstage = function (settings) {
+    var html = '';
+    html += '<div id="' + settings.id + '" />';
+    return html;
+  };
+
+  /**
+   * Theme function for a toolbar container of the Quick Edit module.
+   *
+   * @param {object} settings
+   *   Settings object used to construct the markup.
+   * @param {string} settings.id
+   *   the id to apply to the backstage.
+   *
+   * @return {string}
+   *   The corresponding HTML.
+   */
+  Drupal.theme.quickeditEntityToolbar = function (settings) {
+    var html = '';
+    html += '<div id="' + settings.id + '" class="quickedit quickedit-toolbar-container clearfix">';
+    html += '<i class="quickedit-toolbar-pointer"></i>';
+    html += '<div class="quickedit-toolbar-content">';
+    html += '<div class="quickedit-toolbar quickedit-toolbar-entity clearfix icon icon-pencil">';
+    html += '<div class="quickedit-toolbar-label" />';
+    html += '</div>';
+    html += '<div class="quickedit-toolbar quickedit-toolbar-field clearfix" />';
+    html += '</div><div class="quickedit-toolbar-lining"></div></div>';
+    return html;
+  };
+
+  /**
+   * Theme function for a toolbar container of the Quick Edit module.
+   *
+   * @param {object} settings
+   *   Settings object used to construct the markup.
+   * @param {string} settings.entityLabel
+   *   The title of the active entity.
+   * @param {string} settings.fieldLabel
+   *   The label of the highlighted or active field.
+   *
+   * @return {string}
+   *   The corresponding HTML.
+   */
+  Drupal.theme.quickeditEntityToolbarLabel = function (settings) {
+    // @todo Add XSS regression test coverage in https://www.drupal.org/node/2547437
+    return '<span class="field">' + Drupal.checkPlain(settings.fieldLabel) + '</span>' + Drupal.checkPlain(settings.entityLabel);
+  };
+
+  /**
+   * Element defining a containing box for the placement of the entity toolbar.
+   *
+   * @return {string}
+   *   The corresponding HTML.
+   */
+  Drupal.theme.quickeditEntityToolbarFence = function () {
+    return '<div id="quickedit-toolbar-fence" />';
+  };
+
+  /**
+   * Theme function for a toolbar container of the Quick Edit module.
+   *
+   * @param {object} settings
+   *   Settings object used to construct the markup.
+   * @param {string} settings.id
+   *   The id to apply to the toolbar container.
+   *
+   * @return {string}
+   *   The corresponding HTML.
+   */
+  Drupal.theme.quickeditFieldToolbar = function (settings) {
+    return '<div id="' + settings.id + '" />';
+  };
+
+  /**
+   * Theme function for a toolbar toolgroup of the Quick Edit module.
+   *
+   * @param {object} settings
+   *   Settings object used to construct the markup.
+   * @param {string} [settings.id]
+   *   The id of the toolgroup.
+   * @param {string} settings.classes
+   *   The class of the toolgroup.
+   * @param {Array} settings.buttons
+   *   See {@link Drupal.theme.quickeditButtons}.
+   *
+   * @return {string}
+   *   The corresponding HTML.
+   */
+  Drupal.theme.quickeditToolgroup = function (settings) {
+    // Classes.
+    var classes = (settings.classes || []);
+    classes.unshift('quickedit-toolgroup');
+    var html = '';
+    html += '<div class="' + classes.join(' ') + '"';
+    if (settings.id) {
+      html += ' id="' + settings.id + '"';
+    }
+    html += '>';
+    html += Drupal.theme('quickeditButtons', {buttons: settings.buttons});
+    html += '</div>';
+    return html;
+  };
+
+  /**
+   * Theme function for buttons of the Quick Edit module.
+   *
+   * Can be used for the buttons both in the toolbar toolgroups and in the
+   * modal.
+   *
+   * @param {object} settings
+   *   Settings object used to construct the markup.
+   * @param {Array} settings.buttons
+   * - String type: the type of the button (defaults to 'button')
+   * - Array classes: the classes of the button.
+   * - String label: the label of the button.
+   *
+   * @return {string}
+   *   The corresponding HTML.
+   */
+  Drupal.theme.quickeditButtons = function (settings) {
+    var html = '';
+    for (var i = 0; i < settings.buttons.length; i++) {
+      var button = settings.buttons[i];
+      if (!button.hasOwnProperty('type')) {
+        button.type = 'button';
+      }
+      // Attributes.
+      var attributes = [];
+      var attrMap = settings.buttons[i].attributes || {};
+      for (var attr in attrMap) {
+        if (attrMap.hasOwnProperty(attr)) {
+          attributes.push(attr + ((attrMap[attr]) ? '="' + attrMap[attr] + '"' : ''));
+        }
+      }
+      html += '<button type="' + button.type + '" class="' + button.classes + '"' + ' ' + attributes.join(' ') + '>';
+      html += button.label;
+      html += '</button>';
+    }
+    return html;
+  };
+
+  /**
+   * Theme function for a form container of the Quick Edit module.
+   *
+   * @param {object} settings
+   *   Settings object used to construct the markup.
+   * @param {string} settings.id
+   *   The id to apply to the toolbar container.
+   * @param {string} settings.loadingMsg
+   *   The message to show while loading.
+   *
+   * @return {string}
+   *   The corresponding HTML.
+   */
+  Drupal.theme.quickeditFormContainer = function (settings) {
+    var html = '';
+    html += '<div id="' + settings.id + '" class="quickedit-form-container">';
+    html += '  <div class="quickedit-form">';
+    html += '    <div class="placeholder">';
+    html += settings.loadingMsg;
+    html += '    </div>';
+    html += '  </div>';
+    html += '</div>';
+    return html;
+  };
+
+})(jQuery, Drupal);
diff --git a/core/modules/quickedit/js/theme.js b/core/modules/quickedit/js/theme.js
index 93dc3f238df2..14c3cc3a6c2f 100644
--- a/core/modules/quickedit/js/theme.js
+++ b/core/modules/quickedit/js/theme.js
@@ -1,40 +1,21 @@
 /**
- * @file
- * Provides theme functions for all of Quick Edit's client-side HTML.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/quickedit/js/theme.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   * Theme function for a "backstage" for the Quick Edit module.
-   *
-   * @param {object} settings
-   *   Settings object used to construct the markup.
-   * @param {string} settings.id
-   *   The id to apply to the backstage.
-   *
-   * @return {string}
-   *   The corresponding HTML.
-   */
   Drupal.theme.quickeditBackstage = function (settings) {
     var html = '';
     html += '<div id="' + settings.id + '" />';
     return html;
   };
 
-  /**
-   * Theme function for a toolbar container of the Quick Edit module.
-   *
-   * @param {object} settings
-   *   Settings object used to construct the markup.
-   * @param {string} settings.id
-   *   the id to apply to the backstage.
-   *
-   * @return {string}
-   *   The corresponding HTML.
-   */
   Drupal.theme.quickeditEntityToolbar = function (settings) {
     var html = '';
     html += '<div id="' + settings.id + '" class="quickedit quickedit-toolbar-container clearfix">';
@@ -48,67 +29,20 @@
     return html;
   };
 
-  /**
-   * Theme function for a toolbar container of the Quick Edit module.
-   *
-   * @param {object} settings
-   *   Settings object used to construct the markup.
-   * @param {string} settings.entityLabel
-   *   The title of the active entity.
-   * @param {string} settings.fieldLabel
-   *   The label of the highlighted or active field.
-   *
-   * @return {string}
-   *   The corresponding HTML.
-   */
   Drupal.theme.quickeditEntityToolbarLabel = function (settings) {
-    // @todo Add XSS regression test coverage in https://www.drupal.org/node/2547437
     return '<span class="field">' + Drupal.checkPlain(settings.fieldLabel) + '</span>' + Drupal.checkPlain(settings.entityLabel);
   };
 
-  /**
-   * Element defining a containing box for the placement of the entity toolbar.
-   *
-   * @return {string}
-   *   The corresponding HTML.
-   */
   Drupal.theme.quickeditEntityToolbarFence = function () {
     return '<div id="quickedit-toolbar-fence" />';
   };
 
-  /**
-   * Theme function for a toolbar container of the Quick Edit module.
-   *
-   * @param {object} settings
-   *   Settings object used to construct the markup.
-   * @param {string} settings.id
-   *   The id to apply to the toolbar container.
-   *
-   * @return {string}
-   *   The corresponding HTML.
-   */
   Drupal.theme.quickeditFieldToolbar = function (settings) {
     return '<div id="' + settings.id + '" />';
   };
 
-  /**
-   * Theme function for a toolbar toolgroup of the Quick Edit module.
-   *
-   * @param {object} settings
-   *   Settings object used to construct the markup.
-   * @param {string} [settings.id]
-   *   The id of the toolgroup.
-   * @param {string} settings.classes
-   *   The class of the toolgroup.
-   * @param {Array} settings.buttons
-   *   See {@link Drupal.theme.quickeditButtons}.
-   *
-   * @return {string}
-   *   The corresponding HTML.
-   */
   Drupal.theme.quickeditToolgroup = function (settings) {
-    // Classes.
-    var classes = (settings.classes || []);
+    var classes = settings.classes || [];
     classes.unshift('quickedit-toolgroup');
     var html = '';
     html += '<div class="' + classes.join(' ') + '"';
@@ -116,27 +50,11 @@
       html += ' id="' + settings.id + '"';
     }
     html += '>';
-    html += Drupal.theme('quickeditButtons', {buttons: settings.buttons});
+    html += Drupal.theme('quickeditButtons', { buttons: settings.buttons });
     html += '</div>';
     return html;
   };
 
-  /**
-   * Theme function for buttons of the Quick Edit module.
-   *
-   * Can be used for the buttons both in the toolbar toolgroups and in the
-   * modal.
-   *
-   * @param {object} settings
-   *   Settings object used to construct the markup.
-   * @param {Array} settings.buttons
-   * - String type: the type of the button (defaults to 'button')
-   * - Array classes: the classes of the button.
-   * - String label: the label of the button.
-   *
-   * @return {string}
-   *   The corresponding HTML.
-   */
   Drupal.theme.quickeditButtons = function (settings) {
     var html = '';
     for (var i = 0; i < settings.buttons.length; i++) {
@@ -144,12 +62,12 @@
       if (!button.hasOwnProperty('type')) {
         button.type = 'button';
       }
-      // Attributes.
+
       var attributes = [];
       var attrMap = settings.buttons[i].attributes || {};
       for (var attr in attrMap) {
         if (attrMap.hasOwnProperty(attr)) {
-          attributes.push(attr + ((attrMap[attr]) ? '="' + attrMap[attr] + '"' : ''));
+          attributes.push(attr + (attrMap[attr] ? '="' + attrMap[attr] + '"' : ''));
         }
       }
       html += '<button type="' + button.type + '" class="' + button.classes + '"' + ' ' + attributes.join(' ') + '>';
@@ -159,19 +77,6 @@
     return html;
   };
 
-  /**
-   * Theme function for a form container of the Quick Edit module.
-   *
-   * @param {object} settings
-   *   Settings object used to construct the markup.
-   * @param {string} settings.id
-   *   The id to apply to the toolbar container.
-   * @param {string} settings.loadingMsg
-   *   The message to show while loading.
-   *
-   * @return {string}
-   *   The corresponding HTML.
-   */
   Drupal.theme.quickeditFormContainer = function (settings) {
     var html = '';
     html += '<div id="' + settings.id + '" class="quickedit-form-container">';
@@ -183,5 +88,4 @@
     html += '</div>';
     return html;
   };
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/modules/quickedit/js/util.es6.js b/core/modules/quickedit/js/util.es6.js
new file mode 100644
index 000000000000..4b0a4c43e6e8
--- /dev/null
+++ b/core/modules/quickedit/js/util.es6.js
@@ -0,0 +1,213 @@
+/**
+ * @file
+ * Provides utility functions for Quick Edit.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * @namespace
+   */
+  Drupal.quickedit.util = Drupal.quickedit.util || {};
+
+  /**
+   * @namespace
+   */
+  Drupal.quickedit.util.constants = {};
+
+  /**
+   *
+   * @type {string}
+   */
+  Drupal.quickedit.util.constants.transitionEnd = 'transitionEnd.quickedit webkitTransitionEnd.quickedit transitionend.quickedit msTransitionEnd.quickedit oTransitionEnd.quickedit';
+
+  /**
+   * Converts a field id into a formatted url path.
+   *
+   * @example
+   * Drupal.quickedit.util.buildUrl(
+   *   'node/1/body/und/full',
+   *   '/quickedit/form/!entity_type/!id/!field_name/!langcode/!view_mode'
+   * );
+   *
+   * @param {string} id
+   *   The id of an editable field.
+   * @param {string} urlFormat
+   *   The Controller route for field processing.
+   *
+   * @return {string}
+   *   The formatted URL.
+   */
+  Drupal.quickedit.util.buildUrl = function (id, urlFormat) {
+    var parts = id.split('/');
+    return Drupal.formatString(decodeURIComponent(urlFormat), {
+      '!entity_type': parts[0],
+      '!id': parts[1],
+      '!field_name': parts[2],
+      '!langcode': parts[3],
+      '!view_mode': parts[4]
+    });
+  };
+
+  /**
+   * Shows a network error modal dialog.
+   *
+   * @param {string} title
+   *   The title to use in the modal dialog.
+   * @param {string} message
+   *   The message to use in the modal dialog.
+   */
+  Drupal.quickedit.util.networkErrorModal = function (title, message) {
+    var $message = $('<div>' + message + '</div>');
+    var networkErrorModal = Drupal.dialog($message.get(0), {
+      title: title,
+      dialogClass: 'quickedit-network-error',
+      buttons: [
+        {
+          text: Drupal.t('OK'),
+          click: function () {
+            networkErrorModal.close();
+          },
+          primary: true
+        }
+      ],
+      create: function () {
+        $(this).parent().find('.ui-dialog-titlebar-close').remove();
+      },
+      close: function (event) {
+        // Automatically destroy the DOM element that was used for the dialog.
+        $(event.target).remove();
+      }
+    });
+    networkErrorModal.showModal();
+  };
+
+  /**
+   * @namespace
+   */
+  Drupal.quickedit.util.form = {
+
+    /**
+     * Loads a form, calls a callback to insert.
+     *
+     * Leverages {@link Drupal.Ajax}' ability to have scoped (per-instance)
+     * command implementations to be able to call a callback.
+     *
+     * @param {object} options
+     *   An object with the following keys:
+     * @param {string} options.fieldID
+     *   The field ID that uniquely identifies the field for which this form
+     *   will be loaded.
+     * @param {bool} options.nocssjs
+     *   Boolean indicating whether no CSS and JS should be returned (necessary
+     *   when the form is invisible to the user).
+     * @param {bool} options.reset
+     *   Boolean indicating whether the data stored for this field's entity in
+     *   PrivateTempStore should be used or reset.
+     * @param {function} callback
+     *   A callback function that will receive the form to be inserted, as well
+     *   as the ajax object, necessary if the callback wants to perform other
+     *   Ajax commands.
+     */
+    load: function (options, callback) {
+      var fieldID = options.fieldID;
+
+      // Create a Drupal.ajax instance to load the form.
+      var formLoaderAjax = Drupal.ajax({
+        url: Drupal.quickedit.util.buildUrl(fieldID, Drupal.url('quickedit/form/!entity_type/!id/!field_name/!langcode/!view_mode')),
+        submit: {
+          nocssjs: options.nocssjs,
+          reset: options.reset
+        },
+        error: function (xhr, url) {
+          // Show a modal to inform the user of the network error.
+          var fieldLabel = Drupal.quickedit.metadata.get(fieldID, 'label');
+          var message = Drupal.t('Could not load the form for <q>@field-label</q>, either due to a website problem or a network connection problem.<br>Please try again.', {'@field-label': fieldLabel});
+          Drupal.quickedit.util.networkErrorModal(Drupal.t('Network problem!'), message);
+
+          // Change the state back to "candidate", to allow the user to start
+          // in-place editing of the field again.
+          var fieldModel = Drupal.quickedit.app.model.get('activeField');
+          fieldModel.set('state', 'candidate');
+        }
+      });
+      // Implement a scoped quickeditFieldForm AJAX command: calls the callback.
+      formLoaderAjax.commands.quickeditFieldForm = function (ajax, response, status) {
+        callback(response.data, ajax);
+        Drupal.ajax.instances[this.instanceIndex] = null;
+      };
+      // This will ensure our scoped quickeditFieldForm AJAX command gets
+      // called.
+      formLoaderAjax.execute();
+    },
+
+    /**
+     * Creates a {@link Drupal.Ajax} instance that is used to save a form.
+     *
+     * @param {object} options
+     *   Submit options to the form.
+     * @param {bool} options.nocssjs
+     *   Boolean indicating whether no CSS and JS should be returned (necessary
+     *   when the form is invisible to the user).
+     * @param {Array.<string>} options.other_view_modes
+     *   Array containing view mode IDs (of other instances of this field on the
+     *   page).
+     * @param {jQuery} $submit
+     *   The submit element.
+     *
+     * @return {Drupal.Ajax}
+     *   A {@link Drupal.Ajax} instance.
+     */
+    ajaxifySaving: function (options, $submit) {
+      // Re-wire the form to handle submit.
+      var settings = {
+        url: $submit.closest('form').attr('action'),
+        setClick: true,
+        event: 'click.quickedit',
+        progress: false,
+        submit: {
+          nocssjs: options.nocssjs,
+          other_view_modes: options.other_view_modes
+        },
+
+        /**
+         * Reimplement the success handler.
+         *
+         * Ensure {@link Drupal.attachBehaviors} does not get called on the
+         * form.
+         *
+         * @param {Drupal.AjaxCommands~commandDefinition} response
+         *   The Drupal AJAX response.
+         * @param {number} [status]
+         *   The HTTP status code.
+         */
+        success: function (response, status) {
+          for (var i in response) {
+            if (response.hasOwnProperty(i) && response[i].command && this.commands[response[i].command]) {
+              this.commands[response[i].command](this, response[i], status);
+            }
+          }
+        },
+        base: $submit.attr('id'),
+        element: $submit[0]
+      };
+
+      return Drupal.ajax(settings);
+    },
+
+    /**
+     * Cleans up the {@link Drupal.Ajax} instance that is used to save the form.
+     *
+     * @param {Drupal.Ajax} ajax
+     *   A {@link Drupal.Ajax} instance that was returned by
+     *   {@link Drupal.quickedit.form.ajaxifySaving}.
+     */
+    unajaxifySaving: function (ajax) {
+      $(ajax.element).off('click.quickedit');
+    }
+
+  };
+
+})(jQuery, Drupal);
diff --git a/core/modules/quickedit/js/util.js b/core/modules/quickedit/js/util.js
index 4b0a4c43e6e8..677d5beb8930 100644
--- a/core/modules/quickedit/js/util.js
+++ b/core/modules/quickedit/js/util.js
@@ -1,45 +1,21 @@
 /**
- * @file
- * Provides utility functions for Quick Edit.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/quickedit/js/util.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   * @namespace
-   */
   Drupal.quickedit.util = Drupal.quickedit.util || {};
 
-  /**
-   * @namespace
-   */
   Drupal.quickedit.util.constants = {};
 
-  /**
-   *
-   * @type {string}
-   */
   Drupal.quickedit.util.constants.transitionEnd = 'transitionEnd.quickedit webkitTransitionEnd.quickedit transitionend.quickedit msTransitionEnd.quickedit oTransitionEnd.quickedit';
 
-  /**
-   * Converts a field id into a formatted url path.
-   *
-   * @example
-   * Drupal.quickedit.util.buildUrl(
-   *   'node/1/body/und/full',
-   *   '/quickedit/form/!entity_type/!id/!field_name/!langcode/!view_mode'
-   * );
-   *
-   * @param {string} id
-   *   The id of an editable field.
-   * @param {string} urlFormat
-   *   The Controller route for field processing.
-   *
-   * @return {string}
-   *   The formatted URL.
-   */
   Drupal.quickedit.util.buildUrl = function (id, urlFormat) {
     var parts = id.split('/');
     return Drupal.formatString(decodeURIComponent(urlFormat), {
@@ -51,117 +27,57 @@
     });
   };
 
-  /**
-   * Shows a network error modal dialog.
-   *
-   * @param {string} title
-   *   The title to use in the modal dialog.
-   * @param {string} message
-   *   The message to use in the modal dialog.
-   */
   Drupal.quickedit.util.networkErrorModal = function (title, message) {
     var $message = $('<div>' + message + '</div>');
     var networkErrorModal = Drupal.dialog($message.get(0), {
       title: title,
       dialogClass: 'quickedit-network-error',
-      buttons: [
-        {
-          text: Drupal.t('OK'),
-          click: function () {
-            networkErrorModal.close();
-          },
-          primary: true
-        }
-      ],
-      create: function () {
+      buttons: [{
+        text: Drupal.t('OK'),
+        click: function click() {
+          networkErrorModal.close();
+        },
+        primary: true
+      }],
+      create: function create() {
         $(this).parent().find('.ui-dialog-titlebar-close').remove();
       },
-      close: function (event) {
-        // Automatically destroy the DOM element that was used for the dialog.
+      close: function close(event) {
         $(event.target).remove();
       }
     });
     networkErrorModal.showModal();
   };
 
-  /**
-   * @namespace
-   */
   Drupal.quickedit.util.form = {
-
-    /**
-     * Loads a form, calls a callback to insert.
-     *
-     * Leverages {@link Drupal.Ajax}' ability to have scoped (per-instance)
-     * command implementations to be able to call a callback.
-     *
-     * @param {object} options
-     *   An object with the following keys:
-     * @param {string} options.fieldID
-     *   The field ID that uniquely identifies the field for which this form
-     *   will be loaded.
-     * @param {bool} options.nocssjs
-     *   Boolean indicating whether no CSS and JS should be returned (necessary
-     *   when the form is invisible to the user).
-     * @param {bool} options.reset
-     *   Boolean indicating whether the data stored for this field's entity in
-     *   PrivateTempStore should be used or reset.
-     * @param {function} callback
-     *   A callback function that will receive the form to be inserted, as well
-     *   as the ajax object, necessary if the callback wants to perform other
-     *   Ajax commands.
-     */
-    load: function (options, callback) {
+    load: function load(options, callback) {
       var fieldID = options.fieldID;
 
-      // Create a Drupal.ajax instance to load the form.
       var formLoaderAjax = Drupal.ajax({
         url: Drupal.quickedit.util.buildUrl(fieldID, Drupal.url('quickedit/form/!entity_type/!id/!field_name/!langcode/!view_mode')),
         submit: {
           nocssjs: options.nocssjs,
           reset: options.reset
         },
-        error: function (xhr, url) {
-          // Show a modal to inform the user of the network error.
+        error: function error(xhr, url) {
           var fieldLabel = Drupal.quickedit.metadata.get(fieldID, 'label');
-          var message = Drupal.t('Could not load the form for <q>@field-label</q>, either due to a website problem or a network connection problem.<br>Please try again.', {'@field-label': fieldLabel});
+          var message = Drupal.t('Could not load the form for <q>@field-label</q>, either due to a website problem or a network connection problem.<br>Please try again.', { '@field-label': fieldLabel });
           Drupal.quickedit.util.networkErrorModal(Drupal.t('Network problem!'), message);
 
-          // Change the state back to "candidate", to allow the user to start
-          // in-place editing of the field again.
           var fieldModel = Drupal.quickedit.app.model.get('activeField');
           fieldModel.set('state', 'candidate');
         }
       });
-      // Implement a scoped quickeditFieldForm AJAX command: calls the callback.
+
       formLoaderAjax.commands.quickeditFieldForm = function (ajax, response, status) {
         callback(response.data, ajax);
         Drupal.ajax.instances[this.instanceIndex] = null;
       };
-      // This will ensure our scoped quickeditFieldForm AJAX command gets
-      // called.
+
       formLoaderAjax.execute();
     },
 
-    /**
-     * Creates a {@link Drupal.Ajax} instance that is used to save a form.
-     *
-     * @param {object} options
-     *   Submit options to the form.
-     * @param {bool} options.nocssjs
-     *   Boolean indicating whether no CSS and JS should be returned (necessary
-     *   when the form is invisible to the user).
-     * @param {Array.<string>} options.other_view_modes
-     *   Array containing view mode IDs (of other instances of this field on the
-     *   page).
-     * @param {jQuery} $submit
-     *   The submit element.
-     *
-     * @return {Drupal.Ajax}
-     *   A {@link Drupal.Ajax} instance.
-     */
-    ajaxifySaving: function (options, $submit) {
-      // Re-wire the form to handle submit.
+    ajaxifySaving: function ajaxifySaving(options, $submit) {
       var settings = {
         url: $submit.closest('form').attr('action'),
         setClick: true,
@@ -172,18 +88,7 @@
           other_view_modes: options.other_view_modes
         },
 
-        /**
-         * Reimplement the success handler.
-         *
-         * Ensure {@link Drupal.attachBehaviors} does not get called on the
-         * form.
-         *
-         * @param {Drupal.AjaxCommands~commandDefinition} response
-         *   The Drupal AJAX response.
-         * @param {number} [status]
-         *   The HTTP status code.
-         */
-        success: function (response, status) {
+        success: function success(response, status) {
           for (var i in response) {
             if (response.hasOwnProperty(i) && response[i].command && this.commands[response[i].command]) {
               this.commands[response[i].command](this, response[i], status);
@@ -197,17 +102,9 @@
       return Drupal.ajax(settings);
     },
 
-    /**
-     * Cleans up the {@link Drupal.Ajax} instance that is used to save the form.
-     *
-     * @param {Drupal.Ajax} ajax
-     *   A {@link Drupal.Ajax} instance that was returned by
-     *   {@link Drupal.quickedit.form.ajaxifySaving}.
-     */
-    unajaxifySaving: function (ajax) {
+    unajaxifySaving: function unajaxifySaving(ajax) {
       $(ajax.element).off('click.quickedit');
     }
 
   };
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/modules/quickedit/js/views/AppView.es6.js b/core/modules/quickedit/js/views/AppView.es6.js
new file mode 100644
index 000000000000..b09a110b8bf7
--- /dev/null
+++ b/core/modules/quickedit/js/views/AppView.es6.js
@@ -0,0 +1,600 @@
+/**
+ * @file
+ * A Backbone View that controls the overall "in-place editing application".
+ *
+ * @see Drupal.quickedit.AppModel
+ */
+
+(function ($, _, Backbone, Drupal) {
+
+  'use strict';
+
+  // Indicates whether the page should be reloaded after in-place editing has
+  // shut down. A page reload is necessary to re-instate the original HTML of
+  // the edited fields if in-place editing has been canceled and one or more of
+  // the entity's fields were saved to PrivateTempStore: one of them may have
+  // been changed to the empty value and hence may have been rerendered as the
+  // empty string, which makes it impossible for Quick Edit to know where to
+  // restore the original HTML.
+  var reload = false;
+
+  Drupal.quickedit.AppView = Backbone.View.extend(/** @lends Drupal.quickedit.AppView# */{
+
+    /**
+     * @constructs
+     *
+     * @augments Backbone.View
+     *
+     * @param {object} options
+     *   An object with the following keys:
+     * @param {Drupal.quickedit.AppModel} options.model
+     *   The application state model.
+     * @param {Drupal.quickedit.EntityCollection} options.entitiesCollection
+     *   All on-page entities.
+     * @param {Drupal.quickedit.FieldCollection} options.fieldsCollection
+     *   All on-page fields
+     */
+    initialize: function (options) {
+      // AppView's configuration for handling states.
+      // @see Drupal.quickedit.FieldModel.states
+      this.activeFieldStates = ['activating', 'active'];
+      this.singleFieldStates = ['highlighted', 'activating', 'active'];
+      this.changedFieldStates = ['changed', 'saving', 'saved', 'invalid'];
+      this.readyFieldStates = ['candidate', 'highlighted'];
+
+      // Track app state.
+      this.listenTo(options.entitiesCollection, 'change:state', this.appStateChange);
+      this.listenTo(options.entitiesCollection, 'change:isActive', this.enforceSingleActiveEntity);
+
+      // Track app state.
+      this.listenTo(options.fieldsCollection, 'change:state', this.editorStateChange);
+      // Respond to field model HTML representation change events.
+      this.listenTo(options.fieldsCollection, 'change:html', this.renderUpdatedField);
+      this.listenTo(options.fieldsCollection, 'change:html', this.propagateUpdatedField);
+      // Respond to addition.
+      this.listenTo(options.fieldsCollection, 'add', this.rerenderedFieldToCandidate);
+      // Respond to destruction.
+      this.listenTo(options.fieldsCollection, 'destroy', this.teardownEditor);
+    },
+
+    /**
+     * Handles setup/teardown and state changes when the active entity changes.
+     *
+     * @param {Drupal.quickedit.EntityModel} entityModel
+     *   An instance of the EntityModel class.
+     * @param {string} state
+     *   The state of the associated field. One of
+     *   {@link Drupal.quickedit.EntityModel.states}.
+     */
+    appStateChange: function (entityModel, state) {
+      var app = this;
+      var entityToolbarView;
+      switch (state) {
+        case 'launching':
+          reload = false;
+          // First, create an entity toolbar view.
+          entityToolbarView = new Drupal.quickedit.EntityToolbarView({
+            model: entityModel,
+            appModel: this.model
+          });
+          entityModel.toolbarView = entityToolbarView;
+          // Second, set up in-place editors.
+          // They must be notified of state changes, hence this must happen
+          // while the associated fields are still in the 'inactive' state.
+          entityModel.get('fields').each(function (fieldModel) {
+            app.setupEditor(fieldModel);
+          });
+          // Third, transition the entity to the 'opening' state, which will
+          // transition all fields from 'inactive' to 'candidate'.
+          _.defer(function () {
+            entityModel.set('state', 'opening');
+          });
+          break;
+
+        case 'closed':
+          entityToolbarView = entityModel.toolbarView;
+          // First, tear down the in-place editors.
+          entityModel.get('fields').each(function (fieldModel) {
+            app.teardownEditor(fieldModel);
+          });
+          // Second, tear down the entity toolbar view.
+          if (entityToolbarView) {
+            entityToolbarView.remove();
+            delete entityModel.toolbarView;
+          }
+          // A page reload may be necessary to re-instate the original HTML of
+          // the edited fields.
+          if (reload) {
+            reload = false;
+            location.reload();
+          }
+          break;
+      }
+    },
+
+    /**
+     * Accepts or reject editor (Editor) state changes.
+     *
+     * This is what ensures that the app is in control of what happens.
+     *
+     * @param {string} from
+     *   The previous state.
+     * @param {string} to
+     *   The new state.
+     * @param {null|object} context
+     *   The context that is trying to trigger the state change.
+     * @param {Drupal.quickedit.FieldModel} fieldModel
+     *   The fieldModel to which this change applies.
+     *
+     * @return {bool}
+     *   Whether the editor change was accepted or rejected.
+     */
+    acceptEditorStateChange: function (from, to, context, fieldModel) {
+      var accept = true;
+
+      // If the app is in view mode, then reject all state changes except for
+      // those to 'inactive'.
+      if (context && (context.reason === 'stop' || context.reason === 'rerender')) {
+        if (from === 'candidate' && to === 'inactive') {
+          accept = true;
+        }
+      }
+      // Handling of edit mode state changes is more granular.
+      else {
+        // In general, enforce the states sequence. Disallow going back from a
+        // "later" state to an "earlier" state, except in explicitly allowed
+        // cases.
+        if (!Drupal.quickedit.FieldModel.followsStateSequence(from, to)) {
+          accept = false;
+          // Allow: activating/active -> candidate.
+          // Necessary to stop editing a field.
+          if (_.indexOf(this.activeFieldStates, from) !== -1 && to === 'candidate') {
+            accept = true;
+          }
+          // Allow: changed/invalid -> candidate.
+          // Necessary to stop editing a field when it is changed or invalid.
+          else if ((from === 'changed' || from === 'invalid') && to === 'candidate') {
+            accept = true;
+          }
+          // Allow: highlighted -> candidate.
+          // Necessary to stop highlighting a field.
+          else if (from === 'highlighted' && to === 'candidate') {
+            accept = true;
+          }
+          // Allow: saved -> candidate.
+          // Necessary when successfully saved a field.
+          else if (from === 'saved' && to === 'candidate') {
+            accept = true;
+          }
+          // Allow: invalid -> saving.
+          // Necessary to be able to save a corrected, invalid field.
+          else if (from === 'invalid' && to === 'saving') {
+            accept = true;
+          }
+          // Allow: invalid -> activating.
+          // Necessary to be able to correct a field that turned out to be
+          // invalid after the user already had moved on to the next field
+          // (which we explicitly allow to have a fluent UX).
+          else if (from === 'invalid' && to === 'activating') {
+            accept = true;
+          }
+        }
+
+        // If it's not against the general principle, then here are more
+        // disallowed cases to check.
+        if (accept) {
+          var activeField;
+          var activeFieldState;
+          // Ensure only one field (editor) at a time is active … but allow a
+          // user to hop from one field to the next, even if we still have to
+          // start saving the field that is currently active: assume it will be
+          // valid, to allow for a fluent UX. (If it turns out to be invalid,
+          // this block of code also handles that.)
+          if ((this.readyFieldStates.indexOf(from) !== -1 || from === 'invalid') && this.activeFieldStates.indexOf(to) !== -1) {
+            activeField = this.model.get('activeField');
+            if (activeField && activeField !== fieldModel) {
+              activeFieldState = activeField.get('state');
+              // Allow the state change. If the state of the active field is:
+              // - 'activating' or 'active': change it to 'candidate'
+              // - 'changed' or 'invalid': change it to 'saving'
+              // - 'saving' or 'saved': don't do anything.
+              if (this.activeFieldStates.indexOf(activeFieldState) !== -1) {
+                activeField.set('state', 'candidate');
+              }
+              else if (activeFieldState === 'changed' || activeFieldState === 'invalid') {
+                activeField.set('state', 'saving');
+              }
+
+              // If the field that's being activated is in fact already in the
+              // invalid state (which can only happen because above we allowed
+              // the user to move on to another field to allow for a fluent UX;
+              // we assumed it would be saved successfully), then we shouldn't
+              // allow the field to enter the 'activating' state, instead, we
+              // simply change the active editor. All guarantees and
+              // assumptions for this field still hold!
+              if (from === 'invalid') {
+                this.model.set('activeField', fieldModel);
+                accept = false;
+              }
+              // Do not reject: the field is either in the 'candidate' or
+              // 'highlighted' state and we allow it to enter the 'activating'
+              // state!
+            }
+          }
+          // Reject going from activating/active to candidate because of a
+          // mouseleave.
+          else if (_.indexOf(this.activeFieldStates, from) !== -1 && to === 'candidate') {
+            if (context && context.reason === 'mouseleave') {
+              accept = false;
+            }
+          }
+          // When attempting to stop editing a changed/invalid property, ask for
+          // confirmation.
+          else if ((from === 'changed' || from === 'invalid') && to === 'candidate') {
+            if (context && context.reason === 'mouseleave') {
+              accept = false;
+            }
+            else {
+              // Check whether the transition has been confirmed?
+              if (context && context.confirmed) {
+                accept = true;
+              }
+            }
+          }
+        }
+      }
+
+      return accept;
+    },
+
+    /**
+     * Sets up the in-place editor for the given field.
+     *
+     * Must happen before the fieldModel's state is changed to 'candidate'.
+     *
+     * @param {Drupal.quickedit.FieldModel} fieldModel
+     *   The field for which an in-place editor must be set up.
+     */
+    setupEditor: function (fieldModel) {
+      // Get the corresponding entity toolbar.
+      var entityModel = fieldModel.get('entity');
+      var entityToolbarView = entityModel.toolbarView;
+      // Get the field toolbar DOM root from the entity toolbar.
+      var fieldToolbarRoot = entityToolbarView.getToolbarRoot();
+      // Create in-place editor.
+      var editorName = fieldModel.get('metadata').editor;
+      var editorModel = new Drupal.quickedit.EditorModel();
+      var editorView = new Drupal.quickedit.editors[editorName]({
+        el: $(fieldModel.get('el')),
+        model: editorModel,
+        fieldModel: fieldModel
+      });
+
+      // Create in-place editor's toolbar for this field — stored inside the
+      // entity toolbar, the entity toolbar will position itself appropriately
+      // above (or below) the edited element.
+      var toolbarView = new Drupal.quickedit.FieldToolbarView({
+        el: fieldToolbarRoot,
+        model: fieldModel,
+        $editedElement: $(editorView.getEditedElement()),
+        editorView: editorView,
+        entityModel: entityModel
+      });
+
+      // Create decoration for edited element: padding if necessary, sets
+      // classes on the element to style it according to the current state.
+      var decorationView = new Drupal.quickedit.FieldDecorationView({
+        el: $(editorView.getEditedElement()),
+        model: fieldModel,
+        editorView: editorView
+      });
+
+      // Track these three views in FieldModel so that we can tear them down
+      // correctly.
+      fieldModel.editorView = editorView;
+      fieldModel.toolbarView = toolbarView;
+      fieldModel.decorationView = decorationView;
+    },
+
+    /**
+     * Tears down the in-place editor for the given field.
+     *
+     * Must happen after the fieldModel's state is changed to 'inactive'.
+     *
+     * @param {Drupal.quickedit.FieldModel} fieldModel
+     *   The field for which an in-place editor must be torn down.
+     */
+    teardownEditor: function (fieldModel) {
+      // Early-return if this field was not yet decorated.
+      if (typeof fieldModel.editorView === 'undefined') {
+        return;
+      }
+
+      // Unbind event handlers; remove toolbar element; delete toolbar view.
+      fieldModel.toolbarView.remove();
+      delete fieldModel.toolbarView;
+
+      // Unbind event handlers; delete decoration view. Don't remove the element
+      // because that would remove the field itself.
+      fieldModel.decorationView.remove();
+      delete fieldModel.decorationView;
+
+      // Unbind event handlers; delete editor view. Don't remove the element
+      // because that would remove the field itself.
+      fieldModel.editorView.remove();
+      delete fieldModel.editorView;
+    },
+
+    /**
+     * Asks the user to confirm whether he wants to stop editing via a modal.
+     *
+     * @param {Drupal.quickedit.EntityModel} entityModel
+     *   An instance of the EntityModel class.
+     *
+     * @see Drupal.quickedit.AppView#acceptEditorStateChange
+     */
+    confirmEntityDeactivation: function (entityModel) {
+      var that = this;
+      var discardDialog;
+
+      function closeDiscardDialog(action) {
+        discardDialog.close(action);
+        // The active modal has been removed.
+        that.model.set('activeModal', null);
+
+        // If the targetState is saving, the field must be saved, then the
+        // entity must be saved.
+        if (action === 'save') {
+          entityModel.set('state', 'committing', {confirmed: true});
+        }
+        else {
+          entityModel.set('state', 'deactivating', {confirmed: true});
+          // Editing has been canceled and the changes will not be saved. Mark
+          // the page for reload if the entityModel declares that it requires
+          // a reload.
+          if (entityModel.get('reload')) {
+            reload = true;
+            entityModel.set('reload', false);
+          }
+        }
+      }
+
+      // Only instantiate if there isn't a modal instance visible yet.
+      if (!this.model.get('activeModal')) {
+        var $unsavedChanges = $('<div>' + Drupal.t('You have unsaved changes') + '</div>');
+        discardDialog = Drupal.dialog($unsavedChanges.get(0), {
+          title: Drupal.t('Discard changes?'),
+          dialogClass: 'quickedit-discard-modal',
+          resizable: false,
+          buttons: [
+            {
+              text: Drupal.t('Save'),
+              click: function () {
+                closeDiscardDialog('save');
+              },
+              primary: true
+            },
+            {
+              text: Drupal.t('Discard changes'),
+              click: function () {
+                closeDiscardDialog('discard');
+              }
+            }
+          ],
+          // Prevent this modal from being closed without the user making a
+          // choice as per http://stackoverflow.com/a/5438771.
+          closeOnEscape: false,
+          create: function () {
+            $(this).parent().find('.ui-dialog-titlebar-close').remove();
+          },
+          beforeClose: false,
+          close: function (event) {
+            // Automatically destroy the DOM element that was used for the
+            // dialog.
+            $(event.target).remove();
+          }
+        });
+        this.model.set('activeModal', discardDialog);
+
+        discardDialog.showModal();
+      }
+    },
+
+    /**
+     * Reacts to field state changes; tracks global state.
+     *
+     * @param {Drupal.quickedit.FieldModel} fieldModel
+     *   The `fieldModel` holding the state.
+     * @param {string} state
+     *   The state of the associated field. One of
+     *   {@link Drupal.quickedit.FieldModel.states}.
+     */
+    editorStateChange: function (fieldModel, state) {
+      var from = fieldModel.previous('state');
+      var to = state;
+
+      // Keep track of the highlighted field in the global state.
+      if (_.indexOf(this.singleFieldStates, to) !== -1 && this.model.get('highlightedField') !== fieldModel) {
+        this.model.set('highlightedField', fieldModel);
+      }
+      else if (this.model.get('highlightedField') === fieldModel && to === 'candidate') {
+        this.model.set('highlightedField', null);
+      }
+
+      // Keep track of the active field in the global state.
+      if (_.indexOf(this.activeFieldStates, to) !== -1 && this.model.get('activeField') !== fieldModel) {
+        this.model.set('activeField', fieldModel);
+      }
+      else if (this.model.get('activeField') === fieldModel && to === 'candidate') {
+        // Discarded if it transitions from a changed state to 'candidate'.
+        if (from === 'changed' || from === 'invalid') {
+          fieldModel.editorView.revert();
+        }
+        this.model.set('activeField', null);
+      }
+    },
+
+    /**
+     * Render an updated field (a field whose 'html' attribute changed).
+     *
+     * @param {Drupal.quickedit.FieldModel} fieldModel
+     *   The FieldModel whose 'html' attribute changed.
+     * @param {string} html
+     *   The updated 'html' attribute.
+     * @param {object} options
+     *   An object with the following keys:
+     * @param {bool} options.propagation
+     *   Whether this change to the 'html' attribute occurred because of the
+     *   propagation of changes to another instance of this field.
+     */
+    renderUpdatedField: function (fieldModel, html, options) {
+      // Get data necessary to rerender property before it is unavailable.
+      var $fieldWrapper = $(fieldModel.get('el'));
+      var $context = $fieldWrapper.parent();
+
+      var renderField = function () {
+        // Destroy the field model; this will cause all attached views to be
+        // destroyed too, and removal from all collections in which it exists.
+        fieldModel.destroy();
+
+        // Replace the old content with the new content.
+        $fieldWrapper.replaceWith(html);
+
+        // Attach behaviors again to the modified piece of HTML; this will
+        // create a new field model and call rerenderedFieldToCandidate() with
+        // it.
+        Drupal.attachBehaviors($context.get(0));
+      };
+
+      // When propagating the changes of another instance of this field, this
+      // field is not being actively edited and hence no state changes are
+      // necessary. So: only update the state of this field when the rerendering
+      // of this field happens not because of propagation, but because it is
+      // being edited itself.
+      if (!options.propagation) {
+        // Deferred because renderUpdatedField is reacting to a field model
+        // change event, and we want to make sure that event fully propagates
+        // before making another change to the same model.
+        _.defer(function () {
+          // First set the state to 'candidate', to allow all attached views to
+          // clean up all their "active state"-related changes.
+          fieldModel.set('state', 'candidate');
+
+          // Similarly, the above .set() call's change event must fully
+          // propagate before calling it again.
+          _.defer(function () {
+            // Set the field's state to 'inactive', to enable the updating of
+            // its DOM value.
+            fieldModel.set('state', 'inactive', {reason: 'rerender'});
+
+            renderField();
+          });
+        });
+      }
+      else {
+        renderField();
+      }
+    },
+
+    /**
+     * Propagates changes to an updated field to all instances of that field.
+     *
+     * @param {Drupal.quickedit.FieldModel} updatedField
+     *   The FieldModel whose 'html' attribute changed.
+     * @param {string} html
+     *   The updated 'html' attribute.
+     * @param {object} options
+     *   An object with the following keys:
+     * @param {bool} options.propagation
+     *   Whether this change to the 'html' attribute occurred because of the
+     *   propagation of changes to another instance of this field.
+     *
+     * @see Drupal.quickedit.AppView#renderUpdatedField
+     */
+    propagateUpdatedField: function (updatedField, html, options) {
+      // Don't propagate field updates that themselves were caused by
+      // propagation.
+      if (options.propagation) {
+        return;
+      }
+
+      var htmlForOtherViewModes = updatedField.get('htmlForOtherViewModes');
+      Drupal.quickedit.collections.fields
+        // Find all instances of fields that display the same logical field
+        // (same entity, same field, just a different instance and maybe a
+        // different view mode).
+        .where({logicalFieldID: updatedField.get('logicalFieldID')})
+        .forEach(function (field) {
+          // Ignore the field that was already updated.
+          if (field === updatedField) {
+            return;
+          }
+          // If this other instance of the field has the same view mode, we can
+          // update it easily.
+          else if (field.getViewMode() === updatedField.getViewMode()) {
+            field.set('html', updatedField.get('html'));
+          }
+          // If this other instance of the field has a different view mode, and
+          // that is one of the view modes for which a re-rendered version is
+          // available (and that should be the case unless this field was only
+          // added to the page after editing of the updated field began), then
+          // use that view mode's re-rendered version.
+          else {
+            if (field.getViewMode() in htmlForOtherViewModes) {
+              field.set('html', htmlForOtherViewModes[field.getViewMode()], {propagation: true});
+            }
+          }
+        });
+    },
+
+    /**
+     * If the new in-place editable field is for the entity that's currently
+     * being edited, then transition it to the 'candidate' state.
+     *
+     * This happens when a field was modified, saved and hence rerendered.
+     *
+     * @param {Drupal.quickedit.FieldModel} fieldModel
+     *   A field that was just added to the collection of fields.
+     */
+    rerenderedFieldToCandidate: function (fieldModel) {
+      var activeEntity = Drupal.quickedit.collections.entities.findWhere({isActive: true});
+
+      // Early-return if there is no active entity.
+      if (!activeEntity) {
+        return;
+      }
+
+      // If the field's entity is the active entity, make it a candidate.
+      if (fieldModel.get('entity') === activeEntity) {
+        this.setupEditor(fieldModel);
+        fieldModel.set('state', 'candidate');
+      }
+    },
+
+    /**
+     * EntityModel Collection change handler.
+     *
+     * Handler is called `change:isActive` and enforces a single active entity.
+     *
+     * @param {Drupal.quickedit.EntityModel} changedEntityModel
+     *   The entityModel instance whose active state has changed.
+     */
+    enforceSingleActiveEntity: function (changedEntityModel) {
+      // When an entity is deactivated, we don't need to enforce anything.
+      if (changedEntityModel.get('isActive') === false) {
+        return;
+      }
+
+      // This entity was activated; deactivate all other entities.
+      changedEntityModel.collection.chain()
+        .filter(function (entityModel) {
+          return entityModel.get('isActive') === true && entityModel !== changedEntityModel;
+        })
+        .each(function (entityModel) {
+          entityModel.set('state', 'deactivating');
+        });
+    }
+
+  });
+
+}(jQuery, _, Backbone, Drupal));
diff --git a/core/modules/quickedit/js/views/AppView.js b/core/modules/quickedit/js/views/AppView.js
index b09a110b8bf7..5c7d12c8ec50 100644
--- a/core/modules/quickedit/js/views/AppView.js
+++ b/core/modules/quickedit/js/views/AppView.js
@@ -1,91 +1,54 @@
 /**
- * @file
- * A Backbone View that controls the overall "in-place editing application".
- *
- * @see Drupal.quickedit.AppModel
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/quickedit/js/views/AppView.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, _, Backbone, Drupal) {
 
   'use strict';
 
-  // Indicates whether the page should be reloaded after in-place editing has
-  // shut down. A page reload is necessary to re-instate the original HTML of
-  // the edited fields if in-place editing has been canceled and one or more of
-  // the entity's fields were saved to PrivateTempStore: one of them may have
-  // been changed to the empty value and hence may have been rerendered as the
-  // empty string, which makes it impossible for Quick Edit to know where to
-  // restore the original HTML.
   var reload = false;
 
-  Drupal.quickedit.AppView = Backbone.View.extend(/** @lends Drupal.quickedit.AppView# */{
-
-    /**
-     * @constructs
-     *
-     * @augments Backbone.View
-     *
-     * @param {object} options
-     *   An object with the following keys:
-     * @param {Drupal.quickedit.AppModel} options.model
-     *   The application state model.
-     * @param {Drupal.quickedit.EntityCollection} options.entitiesCollection
-     *   All on-page entities.
-     * @param {Drupal.quickedit.FieldCollection} options.fieldsCollection
-     *   All on-page fields
-     */
-    initialize: function (options) {
-      // AppView's configuration for handling states.
-      // @see Drupal.quickedit.FieldModel.states
+  Drupal.quickedit.AppView = Backbone.View.extend({
+    initialize: function initialize(options) {
       this.activeFieldStates = ['activating', 'active'];
       this.singleFieldStates = ['highlighted', 'activating', 'active'];
       this.changedFieldStates = ['changed', 'saving', 'saved', 'invalid'];
       this.readyFieldStates = ['candidate', 'highlighted'];
 
-      // Track app state.
       this.listenTo(options.entitiesCollection, 'change:state', this.appStateChange);
       this.listenTo(options.entitiesCollection, 'change:isActive', this.enforceSingleActiveEntity);
 
-      // Track app state.
       this.listenTo(options.fieldsCollection, 'change:state', this.editorStateChange);
-      // Respond to field model HTML representation change events.
+
       this.listenTo(options.fieldsCollection, 'change:html', this.renderUpdatedField);
       this.listenTo(options.fieldsCollection, 'change:html', this.propagateUpdatedField);
-      // Respond to addition.
+
       this.listenTo(options.fieldsCollection, 'add', this.rerenderedFieldToCandidate);
-      // Respond to destruction.
+
       this.listenTo(options.fieldsCollection, 'destroy', this.teardownEditor);
     },
 
-    /**
-     * Handles setup/teardown and state changes when the active entity changes.
-     *
-     * @param {Drupal.quickedit.EntityModel} entityModel
-     *   An instance of the EntityModel class.
-     * @param {string} state
-     *   The state of the associated field. One of
-     *   {@link Drupal.quickedit.EntityModel.states}.
-     */
-    appStateChange: function (entityModel, state) {
+    appStateChange: function appStateChange(entityModel, state) {
       var app = this;
       var entityToolbarView;
       switch (state) {
         case 'launching':
           reload = false;
-          // First, create an entity toolbar view.
+
           entityToolbarView = new Drupal.quickedit.EntityToolbarView({
             model: entityModel,
             appModel: this.model
           });
           entityModel.toolbarView = entityToolbarView;
-          // Second, set up in-place editors.
-          // They must be notified of state changes, hence this must happen
-          // while the associated fields are still in the 'inactive' state.
+
           entityModel.get('fields').each(function (fieldModel) {
             app.setupEditor(fieldModel);
           });
-          // Third, transition the entity to the 'opening' state, which will
-          // transition all fields from 'inactive' to 'candidate'.
+
           _.defer(function () {
             entityModel.set('state', 'opening');
           });
@@ -93,17 +56,16 @@
 
         case 'closed':
           entityToolbarView = entityModel.toolbarView;
-          // First, tear down the in-place editors.
+
           entityModel.get('fields').each(function (fieldModel) {
             app.teardownEditor(fieldModel);
           });
-          // Second, tear down the entity toolbar view.
+
           if (entityToolbarView) {
             entityToolbarView.remove();
             delete entityModel.toolbarView;
           }
-          // A page reload may be necessary to re-instate the original HTML of
-          // the edited fields.
+
           if (reload) {
             reload = false;
             location.reload();
@@ -112,156 +74,77 @@
       }
     },
 
-    /**
-     * Accepts or reject editor (Editor) state changes.
-     *
-     * This is what ensures that the app is in control of what happens.
-     *
-     * @param {string} from
-     *   The previous state.
-     * @param {string} to
-     *   The new state.
-     * @param {null|object} context
-     *   The context that is trying to trigger the state change.
-     * @param {Drupal.quickedit.FieldModel} fieldModel
-     *   The fieldModel to which this change applies.
-     *
-     * @return {bool}
-     *   Whether the editor change was accepted or rejected.
-     */
-    acceptEditorStateChange: function (from, to, context, fieldModel) {
+    acceptEditorStateChange: function acceptEditorStateChange(from, to, context, fieldModel) {
       var accept = true;
 
-      // If the app is in view mode, then reject all state changes except for
-      // those to 'inactive'.
       if (context && (context.reason === 'stop' || context.reason === 'rerender')) {
         if (from === 'candidate' && to === 'inactive') {
           accept = true;
         }
-      }
-      // Handling of edit mode state changes is more granular.
-      else {
-        // In general, enforce the states sequence. Disallow going back from a
-        // "later" state to an "earlier" state, except in explicitly allowed
-        // cases.
-        if (!Drupal.quickedit.FieldModel.followsStateSequence(from, to)) {
-          accept = false;
-          // Allow: activating/active -> candidate.
-          // Necessary to stop editing a field.
-          if (_.indexOf(this.activeFieldStates, from) !== -1 && to === 'candidate') {
-            accept = true;
-          }
-          // Allow: changed/invalid -> candidate.
-          // Necessary to stop editing a field when it is changed or invalid.
-          else if ((from === 'changed' || from === 'invalid') && to === 'candidate') {
-            accept = true;
-          }
-          // Allow: highlighted -> candidate.
-          // Necessary to stop highlighting a field.
-          else if (from === 'highlighted' && to === 'candidate') {
-            accept = true;
-          }
-          // Allow: saved -> candidate.
-          // Necessary when successfully saved a field.
-          else if (from === 'saved' && to === 'candidate') {
-            accept = true;
-          }
-          // Allow: invalid -> saving.
-          // Necessary to be able to save a corrected, invalid field.
-          else if (from === 'invalid' && to === 'saving') {
-            accept = true;
-          }
-          // Allow: invalid -> activating.
-          // Necessary to be able to correct a field that turned out to be
-          // invalid after the user already had moved on to the next field
-          // (which we explicitly allow to have a fluent UX).
-          else if (from === 'invalid' && to === 'activating') {
-            accept = true;
-          }
-        }
-
-        // If it's not against the general principle, then here are more
-        // disallowed cases to check.
-        if (accept) {
-          var activeField;
-          var activeFieldState;
-          // Ensure only one field (editor) at a time is active … but allow a
-          // user to hop from one field to the next, even if we still have to
-          // start saving the field that is currently active: assume it will be
-          // valid, to allow for a fluent UX. (If it turns out to be invalid,
-          // this block of code also handles that.)
-          if ((this.readyFieldStates.indexOf(from) !== -1 || from === 'invalid') && this.activeFieldStates.indexOf(to) !== -1) {
-            activeField = this.model.get('activeField');
-            if (activeField && activeField !== fieldModel) {
-              activeFieldState = activeField.get('state');
-              // Allow the state change. If the state of the active field is:
-              // - 'activating' or 'active': change it to 'candidate'
-              // - 'changed' or 'invalid': change it to 'saving'
-              // - 'saving' or 'saved': don't do anything.
-              if (this.activeFieldStates.indexOf(activeFieldState) !== -1) {
-                activeField.set('state', 'candidate');
-              }
-              else if (activeFieldState === 'changed' || activeFieldState === 'invalid') {
-                activeField.set('state', 'saving');
-              }
+      } else {
+          if (!Drupal.quickedit.FieldModel.followsStateSequence(from, to)) {
+            accept = false;
 
-              // If the field that's being activated is in fact already in the
-              // invalid state (which can only happen because above we allowed
-              // the user to move on to another field to allow for a fluent UX;
-              // we assumed it would be saved successfully), then we shouldn't
-              // allow the field to enter the 'activating' state, instead, we
-              // simply change the active editor. All guarantees and
-              // assumptions for this field still hold!
-              if (from === 'invalid') {
-                this.model.set('activeField', fieldModel);
-                accept = false;
-              }
-              // Do not reject: the field is either in the 'candidate' or
-              // 'highlighted' state and we allow it to enter the 'activating'
-              // state!
-            }
-          }
-          // Reject going from activating/active to candidate because of a
-          // mouseleave.
-          else if (_.indexOf(this.activeFieldStates, from) !== -1 && to === 'candidate') {
-            if (context && context.reason === 'mouseleave') {
-              accept = false;
-            }
-          }
-          // When attempting to stop editing a changed/invalid property, ask for
-          // confirmation.
-          else if ((from === 'changed' || from === 'invalid') && to === 'candidate') {
-            if (context && context.reason === 'mouseleave') {
-              accept = false;
-            }
-            else {
-              // Check whether the transition has been confirmed?
-              if (context && context.confirmed) {
+            if (_.indexOf(this.activeFieldStates, from) !== -1 && to === 'candidate') {
+              accept = true;
+            } else if ((from === 'changed' || from === 'invalid') && to === 'candidate') {
                 accept = true;
+              } else if (from === 'highlighted' && to === 'candidate') {
+                  accept = true;
+                } else if (from === 'saved' && to === 'candidate') {
+                    accept = true;
+                  } else if (from === 'invalid' && to === 'saving') {
+                      accept = true;
+                    } else if (from === 'invalid' && to === 'activating') {
+                        accept = true;
+                      }
+          }
+
+          if (accept) {
+            var activeField;
+            var activeFieldState;
+
+            if ((this.readyFieldStates.indexOf(from) !== -1 || from === 'invalid') && this.activeFieldStates.indexOf(to) !== -1) {
+              activeField = this.model.get('activeField');
+              if (activeField && activeField !== fieldModel) {
+                activeFieldState = activeField.get('state');
+
+                if (this.activeFieldStates.indexOf(activeFieldState) !== -1) {
+                  activeField.set('state', 'candidate');
+                } else if (activeFieldState === 'changed' || activeFieldState === 'invalid') {
+                  activeField.set('state', 'saving');
+                }
+
+                if (from === 'invalid') {
+                  this.model.set('activeField', fieldModel);
+                  accept = false;
+                }
               }
-            }
+            } else if (_.indexOf(this.activeFieldStates, from) !== -1 && to === 'candidate') {
+                if (context && context.reason === 'mouseleave') {
+                  accept = false;
+                }
+              } else if ((from === 'changed' || from === 'invalid') && to === 'candidate') {
+                  if (context && context.reason === 'mouseleave') {
+                    accept = false;
+                  } else {
+                    if (context && context.confirmed) {
+                      accept = true;
+                    }
+                  }
+                }
           }
         }
-      }
 
       return accept;
     },
 
-    /**
-     * Sets up the in-place editor for the given field.
-     *
-     * Must happen before the fieldModel's state is changed to 'candidate'.
-     *
-     * @param {Drupal.quickedit.FieldModel} fieldModel
-     *   The field for which an in-place editor must be set up.
-     */
-    setupEditor: function (fieldModel) {
-      // Get the corresponding entity toolbar.
+    setupEditor: function setupEditor(fieldModel) {
       var entityModel = fieldModel.get('entity');
       var entityToolbarView = entityModel.toolbarView;
-      // Get the field toolbar DOM root from the entity toolbar.
+
       var fieldToolbarRoot = entityToolbarView.getToolbarRoot();
-      // Create in-place editor.
+
       var editorName = fieldModel.get('metadata').editor;
       var editorModel = new Drupal.quickedit.EditorModel();
       var editorView = new Drupal.quickedit.editors[editorName]({
@@ -270,9 +153,6 @@
         fieldModel: fieldModel
       });
 
-      // Create in-place editor's toolbar for this field — stored inside the
-      // entity toolbar, the entity toolbar will position itself appropriately
-      // above (or below) the edited element.
       var toolbarView = new Drupal.quickedit.FieldToolbarView({
         el: fieldToolbarRoot,
         model: fieldModel,
@@ -281,77 +161,46 @@
         entityModel: entityModel
       });
 
-      // Create decoration for edited element: padding if necessary, sets
-      // classes on the element to style it according to the current state.
       var decorationView = new Drupal.quickedit.FieldDecorationView({
         el: $(editorView.getEditedElement()),
         model: fieldModel,
         editorView: editorView
       });
 
-      // Track these three views in FieldModel so that we can tear them down
-      // correctly.
       fieldModel.editorView = editorView;
       fieldModel.toolbarView = toolbarView;
       fieldModel.decorationView = decorationView;
     },
 
-    /**
-     * Tears down the in-place editor for the given field.
-     *
-     * Must happen after the fieldModel's state is changed to 'inactive'.
-     *
-     * @param {Drupal.quickedit.FieldModel} fieldModel
-     *   The field for which an in-place editor must be torn down.
-     */
-    teardownEditor: function (fieldModel) {
-      // Early-return if this field was not yet decorated.
+    teardownEditor: function teardownEditor(fieldModel) {
       if (typeof fieldModel.editorView === 'undefined') {
         return;
       }
 
-      // Unbind event handlers; remove toolbar element; delete toolbar view.
       fieldModel.toolbarView.remove();
       delete fieldModel.toolbarView;
 
-      // Unbind event handlers; delete decoration view. Don't remove the element
-      // because that would remove the field itself.
       fieldModel.decorationView.remove();
       delete fieldModel.decorationView;
 
-      // Unbind event handlers; delete editor view. Don't remove the element
-      // because that would remove the field itself.
       fieldModel.editorView.remove();
       delete fieldModel.editorView;
     },
 
-    /**
-     * Asks the user to confirm whether he wants to stop editing via a modal.
-     *
-     * @param {Drupal.quickedit.EntityModel} entityModel
-     *   An instance of the EntityModel class.
-     *
-     * @see Drupal.quickedit.AppView#acceptEditorStateChange
-     */
-    confirmEntityDeactivation: function (entityModel) {
+    confirmEntityDeactivation: function confirmEntityDeactivation(entityModel) {
       var that = this;
       var discardDialog;
 
       function closeDiscardDialog(action) {
         discardDialog.close(action);
-        // The active modal has been removed.
+
         that.model.set('activeModal', null);
 
-        // If the targetState is saving, the field must be saved, then the
-        // entity must be saved.
         if (action === 'save') {
-          entityModel.set('state', 'committing', {confirmed: true});
-        }
-        else {
-          entityModel.set('state', 'deactivating', {confirmed: true});
-          // Editing has been canceled and the changes will not be saved. Mark
-          // the page for reload if the entityModel declares that it requires
-          // a reload.
+          entityModel.set('state', 'committing', { confirmed: true });
+        } else {
+          entityModel.set('state', 'deactivating', { confirmed: true });
+
           if (entityModel.get('reload')) {
             reload = true;
             entityModel.set('reload', false);
@@ -359,38 +208,31 @@
         }
       }
 
-      // Only instantiate if there isn't a modal instance visible yet.
       if (!this.model.get('activeModal')) {
         var $unsavedChanges = $('<div>' + Drupal.t('You have unsaved changes') + '</div>');
         discardDialog = Drupal.dialog($unsavedChanges.get(0), {
           title: Drupal.t('Discard changes?'),
           dialogClass: 'quickedit-discard-modal',
           resizable: false,
-          buttons: [
-            {
-              text: Drupal.t('Save'),
-              click: function () {
-                closeDiscardDialog('save');
-              },
-              primary: true
+          buttons: [{
+            text: Drupal.t('Save'),
+            click: function click() {
+              closeDiscardDialog('save');
             },
-            {
-              text: Drupal.t('Discard changes'),
-              click: function () {
-                closeDiscardDialog('discard');
-              }
+            primary: true
+          }, {
+            text: Drupal.t('Discard changes'),
+            click: function click() {
+              closeDiscardDialog('discard');
             }
-          ],
-          // Prevent this modal from being closed without the user making a
-          // choice as per http://stackoverflow.com/a/5438771.
+          }],
+
           closeOnEscape: false,
-          create: function () {
+          create: function create() {
             $(this).parent().find('.ui-dialog-titlebar-close').remove();
           },
           beforeClose: false,
-          close: function (event) {
-            // Automatically destroy the DOM element that was used for the
-            // dialog.
+          close: function close(event) {
             $(event.target).remove();
           }
         });
@@ -400,33 +242,19 @@
       }
     },
 
-    /**
-     * Reacts to field state changes; tracks global state.
-     *
-     * @param {Drupal.quickedit.FieldModel} fieldModel
-     *   The `fieldModel` holding the state.
-     * @param {string} state
-     *   The state of the associated field. One of
-     *   {@link Drupal.quickedit.FieldModel.states}.
-     */
-    editorStateChange: function (fieldModel, state) {
+    editorStateChange: function editorStateChange(fieldModel, state) {
       var from = fieldModel.previous('state');
       var to = state;
 
-      // Keep track of the highlighted field in the global state.
       if (_.indexOf(this.singleFieldStates, to) !== -1 && this.model.get('highlightedField') !== fieldModel) {
         this.model.set('highlightedField', fieldModel);
-      }
-      else if (this.model.get('highlightedField') === fieldModel && to === 'candidate') {
+      } else if (this.model.get('highlightedField') === fieldModel && to === 'candidate') {
         this.model.set('highlightedField', null);
       }
 
-      // Keep track of the active field in the global state.
       if (_.indexOf(this.activeFieldStates, to) !== -1 && this.model.get('activeField') !== fieldModel) {
         this.model.set('activeField', fieldModel);
-      }
-      else if (this.model.get('activeField') === fieldModel && to === 'candidate') {
-        // Discarded if it transitions from a changed state to 'candidate'.
+      } else if (this.model.get('activeField') === fieldModel && to === 'candidate') {
         if (from === 'changed' || from === 'invalid') {
           fieldModel.editorView.revert();
         }
@@ -434,167 +262,76 @@
       }
     },
 
-    /**
-     * Render an updated field (a field whose 'html' attribute changed).
-     *
-     * @param {Drupal.quickedit.FieldModel} fieldModel
-     *   The FieldModel whose 'html' attribute changed.
-     * @param {string} html
-     *   The updated 'html' attribute.
-     * @param {object} options
-     *   An object with the following keys:
-     * @param {bool} options.propagation
-     *   Whether this change to the 'html' attribute occurred because of the
-     *   propagation of changes to another instance of this field.
-     */
-    renderUpdatedField: function (fieldModel, html, options) {
-      // Get data necessary to rerender property before it is unavailable.
+    renderUpdatedField: function renderUpdatedField(fieldModel, html, options) {
       var $fieldWrapper = $(fieldModel.get('el'));
       var $context = $fieldWrapper.parent();
 
-      var renderField = function () {
-        // Destroy the field model; this will cause all attached views to be
-        // destroyed too, and removal from all collections in which it exists.
+      var renderField = function renderField() {
         fieldModel.destroy();
 
-        // Replace the old content with the new content.
         $fieldWrapper.replaceWith(html);
 
-        // Attach behaviors again to the modified piece of HTML; this will
-        // create a new field model and call rerenderedFieldToCandidate() with
-        // it.
         Drupal.attachBehaviors($context.get(0));
       };
 
-      // When propagating the changes of another instance of this field, this
-      // field is not being actively edited and hence no state changes are
-      // necessary. So: only update the state of this field when the rerendering
-      // of this field happens not because of propagation, but because it is
-      // being edited itself.
       if (!options.propagation) {
-        // Deferred because renderUpdatedField is reacting to a field model
-        // change event, and we want to make sure that event fully propagates
-        // before making another change to the same model.
         _.defer(function () {
-          // First set the state to 'candidate', to allow all attached views to
-          // clean up all their "active state"-related changes.
           fieldModel.set('state', 'candidate');
 
-          // Similarly, the above .set() call's change event must fully
-          // propagate before calling it again.
           _.defer(function () {
-            // Set the field's state to 'inactive', to enable the updating of
-            // its DOM value.
-            fieldModel.set('state', 'inactive', {reason: 'rerender'});
+            fieldModel.set('state', 'inactive', { reason: 'rerender' });
 
             renderField();
           });
         });
-      }
-      else {
+      } else {
         renderField();
       }
     },
 
-    /**
-     * Propagates changes to an updated field to all instances of that field.
-     *
-     * @param {Drupal.quickedit.FieldModel} updatedField
-     *   The FieldModel whose 'html' attribute changed.
-     * @param {string} html
-     *   The updated 'html' attribute.
-     * @param {object} options
-     *   An object with the following keys:
-     * @param {bool} options.propagation
-     *   Whether this change to the 'html' attribute occurred because of the
-     *   propagation of changes to another instance of this field.
-     *
-     * @see Drupal.quickedit.AppView#renderUpdatedField
-     */
-    propagateUpdatedField: function (updatedField, html, options) {
-      // Don't propagate field updates that themselves were caused by
-      // propagation.
+    propagateUpdatedField: function propagateUpdatedField(updatedField, html, options) {
       if (options.propagation) {
         return;
       }
 
       var htmlForOtherViewModes = updatedField.get('htmlForOtherViewModes');
-      Drupal.quickedit.collections.fields
-        // Find all instances of fields that display the same logical field
-        // (same entity, same field, just a different instance and maybe a
-        // different view mode).
-        .where({logicalFieldID: updatedField.get('logicalFieldID')})
-        .forEach(function (field) {
-          // Ignore the field that was already updated.
-          if (field === updatedField) {
-            return;
-          }
-          // If this other instance of the field has the same view mode, we can
-          // update it easily.
-          else if (field.getViewMode() === updatedField.getViewMode()) {
+      Drupal.quickedit.collections.fields.where({ logicalFieldID: updatedField.get('logicalFieldID') }).forEach(function (field) {
+        if (field === updatedField) {
+          return;
+        } else if (field.getViewMode() === updatedField.getViewMode()) {
             field.set('html', updatedField.get('html'));
-          }
-          // If this other instance of the field has a different view mode, and
-          // that is one of the view modes for which a re-rendered version is
-          // available (and that should be the case unless this field was only
-          // added to the page after editing of the updated field began), then
-          // use that view mode's re-rendered version.
-          else {
-            if (field.getViewMode() in htmlForOtherViewModes) {
-              field.set('html', htmlForOtherViewModes[field.getViewMode()], {propagation: true});
+          } else {
+              if (field.getViewMode() in htmlForOtherViewModes) {
+                field.set('html', htmlForOtherViewModes[field.getViewMode()], { propagation: true });
+              }
             }
-          }
-        });
+      });
     },
 
-    /**
-     * If the new in-place editable field is for the entity that's currently
-     * being edited, then transition it to the 'candidate' state.
-     *
-     * This happens when a field was modified, saved and hence rerendered.
-     *
-     * @param {Drupal.quickedit.FieldModel} fieldModel
-     *   A field that was just added to the collection of fields.
-     */
-    rerenderedFieldToCandidate: function (fieldModel) {
-      var activeEntity = Drupal.quickedit.collections.entities.findWhere({isActive: true});
-
-      // Early-return if there is no active entity.
+    rerenderedFieldToCandidate: function rerenderedFieldToCandidate(fieldModel) {
+      var activeEntity = Drupal.quickedit.collections.entities.findWhere({ isActive: true });
+
       if (!activeEntity) {
         return;
       }
 
-      // If the field's entity is the active entity, make it a candidate.
       if (fieldModel.get('entity') === activeEntity) {
         this.setupEditor(fieldModel);
         fieldModel.set('state', 'candidate');
       }
     },
 
-    /**
-     * EntityModel Collection change handler.
-     *
-     * Handler is called `change:isActive` and enforces a single active entity.
-     *
-     * @param {Drupal.quickedit.EntityModel} changedEntityModel
-     *   The entityModel instance whose active state has changed.
-     */
-    enforceSingleActiveEntity: function (changedEntityModel) {
-      // When an entity is deactivated, we don't need to enforce anything.
+    enforceSingleActiveEntity: function enforceSingleActiveEntity(changedEntityModel) {
       if (changedEntityModel.get('isActive') === false) {
         return;
       }
 
-      // This entity was activated; deactivate all other entities.
-      changedEntityModel.collection.chain()
-        .filter(function (entityModel) {
-          return entityModel.get('isActive') === true && entityModel !== changedEntityModel;
-        })
-        .each(function (entityModel) {
-          entityModel.set('state', 'deactivating');
-        });
+      changedEntityModel.collection.chain().filter(function (entityModel) {
+        return entityModel.get('isActive') === true && entityModel !== changedEntityModel;
+      }).each(function (entityModel) {
+        entityModel.set('state', 'deactivating');
+      });
     }
 
   });
-
-}(jQuery, _, Backbone, Drupal));
+})(jQuery, _, Backbone, Drupal);
\ No newline at end of file
diff --git a/core/modules/quickedit/js/views/ContextualLinkView.es6.js b/core/modules/quickedit/js/views/ContextualLinkView.es6.js
new file mode 100644
index 000000000000..bf50f616c7dc
--- /dev/null
+++ b/core/modules/quickedit/js/views/ContextualLinkView.es6.js
@@ -0,0 +1,81 @@
+/**
+ * @file
+ * A Backbone View that provides a dynamic contextual link.
+ */
+
+(function ($, Backbone, Drupal) {
+
+  'use strict';
+
+  Drupal.quickedit.ContextualLinkView = Backbone.View.extend(/** @lends Drupal.quickedit.ContextualLinkView# */{
+
+    /**
+     * Define all events to listen to.
+     *
+     * @return {object}
+     *   A map of events.
+     */
+    events: function () {
+      // Prevents delay and simulated mouse events.
+      function touchEndToClick(event) {
+        event.preventDefault();
+        event.target.click();
+      }
+
+      return {
+        'click a': function (event) {
+          event.preventDefault();
+          this.model.set('state', 'launching');
+        },
+        'touchEnd a': touchEndToClick
+      };
+    },
+
+    /**
+     * Create a new contextual link view.
+     *
+     * @constructs
+     *
+     * @augments Backbone.View
+     *
+     * @param {object} options
+     *   An object with the following keys:
+     * @param {Drupal.quickedit.EntityModel} options.model
+     *   The associated entity's model.
+     * @param {Drupal.quickedit.AppModel} options.appModel
+     *   The application state model.
+     * @param {object} options.strings
+     *   The strings for the "Quick edit" link.
+     */
+    initialize: function (options) {
+      // Insert the text of the quick edit toggle.
+      this.$el.find('a').text(options.strings.quickEdit);
+      // Initial render.
+      this.render();
+      // Re-render whenever this entity's isActive attribute changes.
+      this.listenTo(this.model, 'change:isActive', this.render);
+    },
+
+    /**
+     * Render function for the contextual link view.
+     *
+     * @param {Drupal.quickedit.EntityModel} entityModel
+     *   The associated `EntityModel`.
+     * @param {bool} isActive
+     *   Whether the in-place editor is active or not.
+     *
+     * @return {Drupal.quickedit.ContextualLinkView}
+     *   The `ContextualLinkView` in question.
+     */
+    render: function (entityModel, isActive) {
+      this.$el.find('a').attr('aria-pressed', isActive);
+
+      // Hides the contextual links if an in-place editor is active.
+      this.$el.closest('.contextual').toggle(!isActive);
+
+      return this;
+    }
+
+  });
+
+})(jQuery, Backbone, Drupal);
diff --git a/core/modules/quickedit/js/views/ContextualLinkView.js b/core/modules/quickedit/js/views/ContextualLinkView.js
index bf50f616c7dc..d6ea93ae6ded 100644
--- a/core/modules/quickedit/js/views/ContextualLinkView.js
+++ b/core/modules/quickedit/js/views/ContextualLinkView.js
@@ -1,29 +1,24 @@
 /**
- * @file
- * A Backbone View that provides a dynamic contextual link.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/quickedit/js/views/ContextualLinkView.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Backbone, Drupal) {
 
   'use strict';
 
-  Drupal.quickedit.ContextualLinkView = Backbone.View.extend(/** @lends Drupal.quickedit.ContextualLinkView# */{
-
-    /**
-     * Define all events to listen to.
-     *
-     * @return {object}
-     *   A map of events.
-     */
-    events: function () {
-      // Prevents delay and simulated mouse events.
+  Drupal.quickedit.ContextualLinkView = Backbone.View.extend({
+    events: function events() {
       function touchEndToClick(event) {
         event.preventDefault();
         event.target.click();
       }
 
       return {
-        'click a': function (event) {
+        'click a': function clickA(event) {
           event.preventDefault();
           this.model.set('state', 'launching');
         },
@@ -31,51 +26,21 @@
       };
     },
 
-    /**
-     * Create a new contextual link view.
-     *
-     * @constructs
-     *
-     * @augments Backbone.View
-     *
-     * @param {object} options
-     *   An object with the following keys:
-     * @param {Drupal.quickedit.EntityModel} options.model
-     *   The associated entity's model.
-     * @param {Drupal.quickedit.AppModel} options.appModel
-     *   The application state model.
-     * @param {object} options.strings
-     *   The strings for the "Quick edit" link.
-     */
-    initialize: function (options) {
-      // Insert the text of the quick edit toggle.
+    initialize: function initialize(options) {
       this.$el.find('a').text(options.strings.quickEdit);
-      // Initial render.
+
       this.render();
-      // Re-render whenever this entity's isActive attribute changes.
+
       this.listenTo(this.model, 'change:isActive', this.render);
     },
 
-    /**
-     * Render function for the contextual link view.
-     *
-     * @param {Drupal.quickedit.EntityModel} entityModel
-     *   The associated `EntityModel`.
-     * @param {bool} isActive
-     *   Whether the in-place editor is active or not.
-     *
-     * @return {Drupal.quickedit.ContextualLinkView}
-     *   The `ContextualLinkView` in question.
-     */
-    render: function (entityModel, isActive) {
+    render: function render(entityModel, isActive) {
       this.$el.find('a').attr('aria-pressed', isActive);
 
-      // Hides the contextual links if an in-place editor is active.
       this.$el.closest('.contextual').toggle(!isActive);
 
       return this;
     }
 
   });
-
-})(jQuery, Backbone, Drupal);
+})(jQuery, Backbone, Drupal);
\ No newline at end of file
diff --git a/core/modules/quickedit/js/views/EditorView.es6.js b/core/modules/quickedit/js/views/EditorView.es6.js
new file mode 100644
index 000000000000..5e041db0d4eb
--- /dev/null
+++ b/core/modules/quickedit/js/views/EditorView.es6.js
@@ -0,0 +1,304 @@
+/**
+ * @file
+ * An abstract Backbone View that controls an in-place editor.
+ */
+
+(function ($, Backbone, Drupal) {
+
+  'use strict';
+
+  Drupal.quickedit.EditorView = Backbone.View.extend(/** @lends Drupal.quickedit.EditorView# */{
+
+    /**
+     * A base implementation that outlines the structure for in-place editors.
+     *
+     * Specific in-place editor implementations should subclass (extend) this
+     * View and override whichever method they deem necessary to override.
+     *
+     * Typically you would want to override this method to set the
+     * originalValue attribute in the FieldModel to such a value that your
+     * in-place editor can revert to the original value when necessary.
+     *
+     * @example
+     * <caption>If you override this method, you should call this
+     * method (the parent class' initialize()) first.</caption>
+     * Drupal.quickedit.EditorView.prototype.initialize.call(this, options);
+     *
+     * @constructs
+     *
+     * @augments Backbone.View
+     *
+     * @param {object} options
+     *   An object with the following keys:
+     * @param {Drupal.quickedit.EditorModel} options.model
+     *   The in-place editor state model.
+     * @param {Drupal.quickedit.FieldModel} options.fieldModel
+     *   The field model.
+     *
+     * @see Drupal.quickedit.EditorModel
+     * @see Drupal.quickedit.editors.plain_text
+     */
+    initialize: function (options) {
+      this.fieldModel = options.fieldModel;
+      this.listenTo(this.fieldModel, 'change:state', this.stateChange);
+    },
+
+    /**
+     * @inheritdoc
+     */
+    remove: function () {
+      // The el property is the field, which should not be removed. Remove the
+      // pointer to it, then call Backbone.View.prototype.remove().
+      this.setElement();
+      Backbone.View.prototype.remove.call(this);
+    },
+
+    /**
+     * Returns the edited element.
+     *
+     * For some single cardinality fields, it may be necessary or useful to
+     * not in-place edit (and hence decorate) the DOM element with the
+     * data-quickedit-field-id attribute (which is the field's wrapper), but a
+     * specific element within the field's wrapper.
+     * e.g. using a WYSIWYG editor on a body field should happen on the DOM
+     * element containing the text itself, not on the field wrapper.
+     *
+     * @return {jQuery}
+     *   A jQuery-wrapped DOM element.
+     *
+     * @see Drupal.quickedit.editors.plain_text
+     */
+    getEditedElement: function () {
+      return this.$el;
+    },
+
+    /**
+     *
+     * @return {object}
+     * Returns 3 Quick Edit UI settings that depend on the in-place editor:
+     *  - Boolean padding: indicates whether padding should be applied to the
+     *    edited element, to guarantee legibility of text.
+     *  - Boolean unifiedToolbar: provides the in-place editor with the ability
+     *    to insert its own toolbar UI into Quick Edit's tightly integrated
+     *    toolbar.
+     *  - Boolean fullWidthToolbar: indicates whether Quick Edit's tightly
+     *    integrated toolbar should consume the full width of the element,
+     *    rather than being just long enough to accommodate a label.
+     */
+    getQuickEditUISettings: function () {
+      return {padding: false, unifiedToolbar: false, fullWidthToolbar: false, popup: false};
+    },
+
+    /**
+     * Determines the actions to take given a change of state.
+     *
+     * @param {Drupal.quickedit.FieldModel} fieldModel
+     *   The quickedit `FieldModel` that holds the state.
+     * @param {string} state
+     *   The state of the associated field. One of
+     *   {@link Drupal.quickedit.FieldModel.states}.
+     */
+    stateChange: function (fieldModel, state) {
+      var from = fieldModel.previous('state');
+      var to = state;
+      switch (to) {
+        case 'inactive':
+          // An in-place editor view will not yet exist in this state, hence
+          // this will never be reached. Listed for sake of completeness.
+          break;
+
+        case 'candidate':
+          // Nothing to do for the typical in-place editor: it should not be
+          // visible yet. Except when we come from the 'invalid' state, then we
+          // clean up.
+          if (from === 'invalid') {
+            this.removeValidationErrors();
+          }
+          break;
+
+        case 'highlighted':
+          // Nothing to do for the typical in-place editor: it should not be
+          // visible yet.
+          break;
+
+        case 'activating':
+          // The user has indicated he wants to do in-place editing: if
+          // something needs to be loaded (CSS/JavaScript/server data/…), then
+          // do so at this stage, and once the in-place editor is ready,
+          // set the 'active' state. A "loading" indicator will be shown in the
+          // UI for as long as the field remains in this state.
+          var loadDependencies = function (callback) {
+            // Do the loading here.
+            callback();
+          };
+          loadDependencies(function () {
+            fieldModel.set('state', 'active');
+          });
+          break;
+
+        case 'active':
+          // The user can now actually use the in-place editor.
+          break;
+
+        case 'changed':
+          // Nothing to do for the typical in-place editor. The UI will show an
+          // indicator that the field has changed.
+          break;
+
+        case 'saving':
+          // When the user has indicated he wants to save his changes to this
+          // field, this state will be entered. If the previous saving attempt
+          // resulted in validation errors, the previous state will be
+          // 'invalid'. Clean up those validation errors while the user is
+          // saving.
+          if (from === 'invalid') {
+            this.removeValidationErrors();
+          }
+          this.save();
+          break;
+
+        case 'saved':
+          // Nothing to do for the typical in-place editor. Immediately after
+          // being saved, a field will go to the 'candidate' state, where it
+          // should no longer be visible (after all, the field will then again
+          // just be a *candidate* to be in-place edited).
+          break;
+
+        case 'invalid':
+          // The modified field value was attempted to be saved, but there were
+          // validation errors.
+          this.showValidationErrors();
+          break;
+      }
+    },
+
+    /**
+     * Reverts the modified value to the original, before editing started.
+     */
+    revert: function () {
+      // A no-op by default; each editor should implement reverting itself.
+      // Note that if the in-place editor does not cause the FieldModel's
+      // element to be modified, then nothing needs to happen.
+    },
+
+    /**
+     * Saves the modified value in the in-place editor for this field.
+     */
+    save: function () {
+      var fieldModel = this.fieldModel;
+      var editorModel = this.model;
+      var backstageId = 'quickedit_backstage-' + this.fieldModel.id.replace(/[\/\[\]\_\s]/g, '-');
+
+      function fillAndSubmitForm(value) {
+        var $form = $('#' + backstageId).find('form');
+        // Fill in the value in any <input> that isn't hidden or a submit
+        // button.
+        $form.find(':input[type!="hidden"][type!="submit"]:not(select)')
+          // Don't mess with the node summary.
+          .not('[name$="\\[summary\\]"]').val(value);
+        // Submit the form.
+        $form.find('.quickedit-form-submit').trigger('click.quickedit');
+      }
+
+      var formOptions = {
+        fieldID: this.fieldModel.get('fieldID'),
+        $el: this.$el,
+        nocssjs: true,
+        other_view_modes: fieldModel.findOtherViewModes(),
+        // Reset an existing entry for this entity in the PrivateTempStore (if
+        // any) when saving the field. Logically speaking, this should happen in
+        // a separate request because this is an entity-level operation, not a
+        // field-level operation. But that would require an additional request,
+        // that might not even be necessary: it is only when a user saves a
+        // first changed field for an entity that this needs to happen:
+        // precisely now!
+        reset: !this.fieldModel.get('entity').get('inTempStore')
+      };
+
+      var self = this;
+      Drupal.quickedit.util.form.load(formOptions, function (form, ajax) {
+        // Create a backstage area for storing forms that are hidden from view
+        // (hence "backstage" — since the editing doesn't happen in the form, it
+        // happens "directly" in the content, the form is only used for saving).
+        var $backstage = $(Drupal.theme('quickeditBackstage', {id: backstageId})).appendTo('body');
+        // Hidden forms are stuffed into the backstage container for this field.
+        var $form = $(form).appendTo($backstage);
+        // Disable the browser's HTML5 validation; we only care about server-
+        // side validation. (Not disabling this will actually cause problems
+        // because browsers don't like to set HTML5 validation errors on hidden
+        // forms.)
+        $form.prop('novalidate', true);
+        var $submit = $form.find('.quickedit-form-submit');
+        self.formSaveAjax = Drupal.quickedit.util.form.ajaxifySaving(formOptions, $submit);
+
+        function removeHiddenForm() {
+          Drupal.quickedit.util.form.unajaxifySaving(self.formSaveAjax);
+          delete self.formSaveAjax;
+          $backstage.remove();
+        }
+
+        // Successfully saved.
+        self.formSaveAjax.commands.quickeditFieldFormSaved = function (ajax, response, status) {
+          removeHiddenForm();
+          // First, transition the state to 'saved'.
+          fieldModel.set('state', 'saved');
+          // Second, set the 'htmlForOtherViewModes' attribute, so that when
+          // this field is rerendered, the change can be propagated to other
+          // instances of this field, which may be displayed in different view
+          // modes.
+          fieldModel.set('htmlForOtherViewModes', response.other_view_modes);
+          // Finally, set the 'html' attribute on the field model. This will
+          // cause the field to be rerendered.
+          fieldModel.set('html', response.data);
+        };
+
+        // Unsuccessfully saved; validation errors.
+        self.formSaveAjax.commands.quickeditFieldFormValidationErrors = function (ajax, response, status) {
+          removeHiddenForm();
+          editorModel.set('validationErrors', response.data);
+          fieldModel.set('state', 'invalid');
+        };
+
+        // The quickeditFieldForm AJAX command is only called upon loading the
+        // form for the first time, and when there are validation errors in the
+        // form; Form API then marks which form items have errors. This is
+        // useful for the form-based in-place editor, but pointless for any
+        // other: the form itself won't be visible at all anyway! So, we just
+        // ignore it.
+        self.formSaveAjax.commands.quickeditFieldForm = function () {};
+
+        fillAndSubmitForm(editorModel.get('currentValue'));
+      });
+    },
+
+    /**
+     * Shows validation error messages.
+     *
+     * Should be called when the state is changed to 'invalid'.
+     */
+    showValidationErrors: function () {
+      var $errors = $('<div class="quickedit-validation-errors"></div>')
+        .append(this.model.get('validationErrors'));
+      this.getEditedElement()
+        .addClass('quickedit-validation-error')
+        .after($errors);
+    },
+
+    /**
+     * Cleans up validation error messages.
+     *
+     * Should be called when the state is changed to 'candidate' or 'saving'. In
+     * the case of the latter: the user has modified the value in the in-place
+     * editor again to attempt to save again. In the case of the latter: the
+     * invalid value was discarded.
+     */
+    removeValidationErrors: function () {
+      this.getEditedElement()
+        .removeClass('quickedit-validation-error')
+        .next('.quickedit-validation-errors')
+        .remove();
+    }
+
+  });
+
+}(jQuery, Backbone, Drupal));
diff --git a/core/modules/quickedit/js/views/EditorView.js b/core/modules/quickedit/js/views/EditorView.js
index 5e041db0d4eb..c0456ac99f3a 100644
--- a/core/modules/quickedit/js/views/EditorView.js
+++ b/core/modules/quickedit/js/views/EditorView.js
@@ -1,134 +1,52 @@
 /**
- * @file
- * An abstract Backbone View that controls an in-place editor.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/quickedit/js/views/EditorView.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Backbone, Drupal) {
 
   'use strict';
 
-  Drupal.quickedit.EditorView = Backbone.View.extend(/** @lends Drupal.quickedit.EditorView# */{
-
-    /**
-     * A base implementation that outlines the structure for in-place editors.
-     *
-     * Specific in-place editor implementations should subclass (extend) this
-     * View and override whichever method they deem necessary to override.
-     *
-     * Typically you would want to override this method to set the
-     * originalValue attribute in the FieldModel to such a value that your
-     * in-place editor can revert to the original value when necessary.
-     *
-     * @example
-     * <caption>If you override this method, you should call this
-     * method (the parent class' initialize()) first.</caption>
-     * Drupal.quickedit.EditorView.prototype.initialize.call(this, options);
-     *
-     * @constructs
-     *
-     * @augments Backbone.View
-     *
-     * @param {object} options
-     *   An object with the following keys:
-     * @param {Drupal.quickedit.EditorModel} options.model
-     *   The in-place editor state model.
-     * @param {Drupal.quickedit.FieldModel} options.fieldModel
-     *   The field model.
-     *
-     * @see Drupal.quickedit.EditorModel
-     * @see Drupal.quickedit.editors.plain_text
-     */
-    initialize: function (options) {
+  Drupal.quickedit.EditorView = Backbone.View.extend({
+    initialize: function initialize(options) {
       this.fieldModel = options.fieldModel;
       this.listenTo(this.fieldModel, 'change:state', this.stateChange);
     },
 
-    /**
-     * @inheritdoc
-     */
-    remove: function () {
-      // The el property is the field, which should not be removed. Remove the
-      // pointer to it, then call Backbone.View.prototype.remove().
+    remove: function remove() {
       this.setElement();
       Backbone.View.prototype.remove.call(this);
     },
 
-    /**
-     * Returns the edited element.
-     *
-     * For some single cardinality fields, it may be necessary or useful to
-     * not in-place edit (and hence decorate) the DOM element with the
-     * data-quickedit-field-id attribute (which is the field's wrapper), but a
-     * specific element within the field's wrapper.
-     * e.g. using a WYSIWYG editor on a body field should happen on the DOM
-     * element containing the text itself, not on the field wrapper.
-     *
-     * @return {jQuery}
-     *   A jQuery-wrapped DOM element.
-     *
-     * @see Drupal.quickedit.editors.plain_text
-     */
-    getEditedElement: function () {
+    getEditedElement: function getEditedElement() {
       return this.$el;
     },
 
-    /**
-     *
-     * @return {object}
-     * Returns 3 Quick Edit UI settings that depend on the in-place editor:
-     *  - Boolean padding: indicates whether padding should be applied to the
-     *    edited element, to guarantee legibility of text.
-     *  - Boolean unifiedToolbar: provides the in-place editor with the ability
-     *    to insert its own toolbar UI into Quick Edit's tightly integrated
-     *    toolbar.
-     *  - Boolean fullWidthToolbar: indicates whether Quick Edit's tightly
-     *    integrated toolbar should consume the full width of the element,
-     *    rather than being just long enough to accommodate a label.
-     */
-    getQuickEditUISettings: function () {
-      return {padding: false, unifiedToolbar: false, fullWidthToolbar: false, popup: false};
+    getQuickEditUISettings: function getQuickEditUISettings() {
+      return { padding: false, unifiedToolbar: false, fullWidthToolbar: false, popup: false };
     },
 
-    /**
-     * Determines the actions to take given a change of state.
-     *
-     * @param {Drupal.quickedit.FieldModel} fieldModel
-     *   The quickedit `FieldModel` that holds the state.
-     * @param {string} state
-     *   The state of the associated field. One of
-     *   {@link Drupal.quickedit.FieldModel.states}.
-     */
-    stateChange: function (fieldModel, state) {
+    stateChange: function stateChange(fieldModel, state) {
       var from = fieldModel.previous('state');
       var to = state;
       switch (to) {
         case 'inactive':
-          // An in-place editor view will not yet exist in this state, hence
-          // this will never be reached. Listed for sake of completeness.
           break;
 
         case 'candidate':
-          // Nothing to do for the typical in-place editor: it should not be
-          // visible yet. Except when we come from the 'invalid' state, then we
-          // clean up.
           if (from === 'invalid') {
             this.removeValidationErrors();
           }
           break;
 
         case 'highlighted':
-          // Nothing to do for the typical in-place editor: it should not be
-          // visible yet.
           break;
 
         case 'activating':
-          // The user has indicated he wants to do in-place editing: if
-          // something needs to be loaded (CSS/JavaScript/server data/…), then
-          // do so at this stage, and once the in-place editor is ready,
-          // set the 'active' state. A "loading" indicator will be shown in the
-          // UI for as long as the field remains in this state.
-          var loadDependencies = function (callback) {
-            // Do the loading here.
+          var loadDependencies = function loadDependencies(callback) {
             callback();
           };
           loadDependencies(function () {
@@ -137,20 +55,12 @@
           break;
 
         case 'active':
-          // The user can now actually use the in-place editor.
           break;
 
         case 'changed':
-          // Nothing to do for the typical in-place editor. The UI will show an
-          // indicator that the field has changed.
           break;
 
         case 'saving':
-          // When the user has indicated he wants to save his changes to this
-          // field, this state will be entered. If the previous saving attempt
-          // resulted in validation errors, the previous state will be
-          // 'invalid'. Clean up those validation errors while the user is
-          // saving.
           if (from === 'invalid') {
             this.removeValidationErrors();
           }
@@ -158,45 +68,26 @@
           break;
 
         case 'saved':
-          // Nothing to do for the typical in-place editor. Immediately after
-          // being saved, a field will go to the 'candidate' state, where it
-          // should no longer be visible (after all, the field will then again
-          // just be a *candidate* to be in-place edited).
           break;
 
         case 'invalid':
-          // The modified field value was attempted to be saved, but there were
-          // validation errors.
           this.showValidationErrors();
           break;
       }
     },
 
-    /**
-     * Reverts the modified value to the original, before editing started.
-     */
-    revert: function () {
-      // A no-op by default; each editor should implement reverting itself.
-      // Note that if the in-place editor does not cause the FieldModel's
-      // element to be modified, then nothing needs to happen.
-    },
+    revert: function revert() {},
 
-    /**
-     * Saves the modified value in the in-place editor for this field.
-     */
-    save: function () {
+    save: function save() {
       var fieldModel = this.fieldModel;
       var editorModel = this.model;
       var backstageId = 'quickedit_backstage-' + this.fieldModel.id.replace(/[\/\[\]\_\s]/g, '-');
 
       function fillAndSubmitForm(value) {
         var $form = $('#' + backstageId).find('form');
-        // Fill in the value in any <input> that isn't hidden or a submit
-        // button.
-        $form.find(':input[type!="hidden"][type!="submit"]:not(select)')
-          // Don't mess with the node summary.
-          .not('[name$="\\[summary\\]"]').val(value);
-        // Submit the form.
+
+        $form.find(':input[type!="hidden"][type!="submit"]:not(select)').not('[name$="\\[summary\\]"]').val(value);
+
         $form.find('.quickedit-form-submit').trigger('click.quickedit');
       }
 
@@ -205,28 +96,16 @@
         $el: this.$el,
         nocssjs: true,
         other_view_modes: fieldModel.findOtherViewModes(),
-        // Reset an existing entry for this entity in the PrivateTempStore (if
-        // any) when saving the field. Logically speaking, this should happen in
-        // a separate request because this is an entity-level operation, not a
-        // field-level operation. But that would require an additional request,
-        // that might not even be necessary: it is only when a user saves a
-        // first changed field for an entity that this needs to happen:
-        // precisely now!
+
         reset: !this.fieldModel.get('entity').get('inTempStore')
       };
 
       var self = this;
       Drupal.quickedit.util.form.load(formOptions, function (form, ajax) {
-        // Create a backstage area for storing forms that are hidden from view
-        // (hence "backstage" — since the editing doesn't happen in the form, it
-        // happens "directly" in the content, the form is only used for saving).
-        var $backstage = $(Drupal.theme('quickeditBackstage', {id: backstageId})).appendTo('body');
-        // Hidden forms are stuffed into the backstage container for this field.
+        var $backstage = $(Drupal.theme('quickeditBackstage', { id: backstageId })).appendTo('body');
+
         var $form = $(form).appendTo($backstage);
-        // Disable the browser's HTML5 validation; we only care about server-
-        // side validation. (Not disabling this will actually cause problems
-        // because browsers don't like to set HTML5 validation errors on hidden
-        // forms.)
+
         $form.prop('novalidate', true);
         var $submit = $form.find('.quickedit-form-submit');
         self.formSaveAjax = Drupal.quickedit.util.form.ajaxifySaving(formOptions, $submit);
@@ -237,68 +116,36 @@
           $backstage.remove();
         }
 
-        // Successfully saved.
         self.formSaveAjax.commands.quickeditFieldFormSaved = function (ajax, response, status) {
           removeHiddenForm();
-          // First, transition the state to 'saved'.
+
           fieldModel.set('state', 'saved');
-          // Second, set the 'htmlForOtherViewModes' attribute, so that when
-          // this field is rerendered, the change can be propagated to other
-          // instances of this field, which may be displayed in different view
-          // modes.
+
           fieldModel.set('htmlForOtherViewModes', response.other_view_modes);
-          // Finally, set the 'html' attribute on the field model. This will
-          // cause the field to be rerendered.
+
           fieldModel.set('html', response.data);
         };
 
-        // Unsuccessfully saved; validation errors.
         self.formSaveAjax.commands.quickeditFieldFormValidationErrors = function (ajax, response, status) {
           removeHiddenForm();
           editorModel.set('validationErrors', response.data);
           fieldModel.set('state', 'invalid');
         };
 
-        // The quickeditFieldForm AJAX command is only called upon loading the
-        // form for the first time, and when there are validation errors in the
-        // form; Form API then marks which form items have errors. This is
-        // useful for the form-based in-place editor, but pointless for any
-        // other: the form itself won't be visible at all anyway! So, we just
-        // ignore it.
         self.formSaveAjax.commands.quickeditFieldForm = function () {};
 
         fillAndSubmitForm(editorModel.get('currentValue'));
       });
     },
 
-    /**
-     * Shows validation error messages.
-     *
-     * Should be called when the state is changed to 'invalid'.
-     */
-    showValidationErrors: function () {
-      var $errors = $('<div class="quickedit-validation-errors"></div>')
-        .append(this.model.get('validationErrors'));
-      this.getEditedElement()
-        .addClass('quickedit-validation-error')
-        .after($errors);
+    showValidationErrors: function showValidationErrors() {
+      var $errors = $('<div class="quickedit-validation-errors"></div>').append(this.model.get('validationErrors'));
+      this.getEditedElement().addClass('quickedit-validation-error').after($errors);
     },
 
-    /**
-     * Cleans up validation error messages.
-     *
-     * Should be called when the state is changed to 'candidate' or 'saving'. In
-     * the case of the latter: the user has modified the value in the in-place
-     * editor again to attempt to save again. In the case of the latter: the
-     * invalid value was discarded.
-     */
-    removeValidationErrors: function () {
-      this.getEditedElement()
-        .removeClass('quickedit-validation-error')
-        .next('.quickedit-validation-errors')
-        .remove();
+    removeValidationErrors: function removeValidationErrors() {
+      this.getEditedElement().removeClass('quickedit-validation-error').next('.quickedit-validation-errors').remove();
     }
 
   });
-
-}(jQuery, Backbone, Drupal));
+})(jQuery, Backbone, Drupal);
\ No newline at end of file
diff --git a/core/modules/quickedit/js/views/EntityDecorationView.es6.js b/core/modules/quickedit/js/views/EntityDecorationView.es6.js
new file mode 100644
index 000000000000..ff090fe4c763
--- /dev/null
+++ b/core/modules/quickedit/js/views/EntityDecorationView.es6.js
@@ -0,0 +1,40 @@
+/**
+ * @file
+ * A Backbone view that decorates the in-place editable entity.
+ */
+
+(function (Drupal, $, Backbone) {
+
+  'use strict';
+
+  Drupal.quickedit.EntityDecorationView = Backbone.View.extend(/** @lends Drupal.quickedit.EntityDecorationView# */{
+
+    /**
+     * Associated with the DOM root node of an editable entity.
+     *
+     * @constructs
+     *
+     * @augments Backbone.View
+     */
+    initialize: function () {
+      this.listenTo(this.model, 'change', this.render);
+    },
+
+    /**
+     * @inheritdoc
+     */
+    render: function () {
+      this.$el.toggleClass('quickedit-entity-active', this.model.get('isActive'));
+    },
+
+    /**
+     * @inheritdoc
+     */
+    remove: function () {
+      this.setElement(null);
+      Backbone.View.prototype.remove.call(this);
+    }
+
+  });
+
+}(Drupal, jQuery, Backbone));
diff --git a/core/modules/quickedit/js/views/EntityDecorationView.js b/core/modules/quickedit/js/views/EntityDecorationView.js
index ff090fe4c763..9233696db32c 100644
--- a/core/modules/quickedit/js/views/EntityDecorationView.js
+++ b/core/modules/quickedit/js/views/EntityDecorationView.js
@@ -1,40 +1,28 @@
 /**
- * @file
- * A Backbone view that decorates the in-place editable entity.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/quickedit/js/views/EntityDecorationView.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function (Drupal, $, Backbone) {
 
   'use strict';
 
-  Drupal.quickedit.EntityDecorationView = Backbone.View.extend(/** @lends Drupal.quickedit.EntityDecorationView# */{
-
-    /**
-     * Associated with the DOM root node of an editable entity.
-     *
-     * @constructs
-     *
-     * @augments Backbone.View
-     */
-    initialize: function () {
+  Drupal.quickedit.EntityDecorationView = Backbone.View.extend({
+    initialize: function initialize() {
       this.listenTo(this.model, 'change', this.render);
     },
 
-    /**
-     * @inheritdoc
-     */
-    render: function () {
+    render: function render() {
       this.$el.toggleClass('quickedit-entity-active', this.model.get('isActive'));
     },
 
-    /**
-     * @inheritdoc
-     */
-    remove: function () {
+    remove: function remove() {
       this.setElement(null);
       Backbone.View.prototype.remove.call(this);
     }
 
   });
-
-}(Drupal, jQuery, Backbone));
+})(Drupal, jQuery, Backbone);
\ No newline at end of file
diff --git a/core/modules/quickedit/js/views/EntityToolbarView.es6.js b/core/modules/quickedit/js/views/EntityToolbarView.es6.js
new file mode 100644
index 000000000000..4fa0506f5382
--- /dev/null
+++ b/core/modules/quickedit/js/views/EntityToolbarView.es6.js
@@ -0,0 +1,528 @@
+/**
+ * @file
+ * A Backbone View that provides an entity level toolbar.
+ */
+
+(function ($, _, Backbone, Drupal, debounce) {
+
+  'use strict';
+
+  Drupal.quickedit.EntityToolbarView = Backbone.View.extend(/** @lends Drupal.quickedit.EntityToolbarView# */{
+
+    /**
+     * @type {jQuery}
+     */
+    _fieldToolbarRoot: null,
+
+    /**
+     * @return {object}
+     *   A map of events.
+     */
+    events: function () {
+      var map = {
+        'click button.action-save': 'onClickSave',
+        'click button.action-cancel': 'onClickCancel',
+        'mouseenter': 'onMouseenter'
+      };
+      return map;
+    },
+
+    /**
+     * @constructs
+     *
+     * @augments Backbone.View
+     *
+     * @param {object} options
+     *   Options to construct the view.
+     * @param {Drupal.quickedit.AppModel} options.appModel
+     *   A quickedit `AppModel` to use in the view.
+     */
+    initialize: function (options) {
+      var that = this;
+      this.appModel = options.appModel;
+      this.$entity = $(this.model.get('el'));
+
+      // Rerender whenever the entity state changes.
+      this.listenTo(this.model, 'change:isActive change:isDirty change:state', this.render);
+      // Also rerender whenever a different field is highlighted or activated.
+      this.listenTo(this.appModel, 'change:highlightedField change:activeField', this.render);
+      // Rerender when a field of the entity changes state.
+      this.listenTo(this.model.get('fields'), 'change:state', this.fieldStateChange);
+
+      // Reposition the entity toolbar as the viewport and the position within
+      // the viewport changes.
+      $(window).on('resize.quickedit scroll.quickedit drupalViewportOffsetChange.quickedit', debounce($.proxy(this.windowChangeHandler, this), 150));
+
+      // Adjust the fence placement within which the entity toolbar may be
+      // positioned.
+      $(document).on('drupalViewportOffsetChange.quickedit', function (event, offsets) {
+        if (that.$fence) {
+          that.$fence.css(offsets);
+        }
+      });
+
+      // Set the entity toolbar DOM element as the el for this view.
+      var $toolbar = this.buildToolbarEl();
+      this.setElement($toolbar);
+      this._fieldToolbarRoot = $toolbar.find('.quickedit-toolbar-field').get(0);
+
+      // Initial render.
+      this.render();
+    },
+
+    /**
+     * @inheritdoc
+     *
+     * @return {Drupal.quickedit.EntityToolbarView}
+     *   The entity toolbar view.
+     */
+    render: function () {
+      if (this.model.get('isActive')) {
+        // If the toolbar container doesn't exist, create it.
+        var $body = $('body');
+        if ($body.children('#quickedit-entity-toolbar').length === 0) {
+          $body.append(this.$el);
+        }
+        // The fence will define a area on the screen that the entity toolbar
+        // will be position within.
+        if ($body.children('#quickedit-toolbar-fence').length === 0) {
+          this.$fence = $(Drupal.theme('quickeditEntityToolbarFence'))
+            .css(Drupal.displace())
+            .appendTo($body);
+        }
+        // Adds the entity title to the toolbar.
+        this.label();
+
+        // Show the save and cancel buttons.
+        this.show('ops');
+        // If render is being called and the toolbar is already visible, just
+        // reposition it.
+        this.position();
+      }
+
+      // The save button text and state varies with the state of the entity
+      // model.
+      var $button = this.$el.find('.quickedit-button.action-save');
+      var isDirty = this.model.get('isDirty');
+      // Adjust the save button according to the state of the model.
+      switch (this.model.get('state')) {
+        // Quick editing is active, but no field is being edited.
+        case 'opened':
+          // The saving throbber is not managed by AJAX system. The
+          // EntityToolbarView manages this visual element.
+          $button
+            .removeClass('action-saving icon-throbber icon-end')
+            .text(Drupal.t('Save'))
+            .removeAttr('disabled')
+            .attr('aria-hidden', !isDirty);
+          break;
+
+        // The changes to the fields of the entity are being committed.
+        case 'committing':
+          $button
+            .addClass('action-saving icon-throbber icon-end')
+            .text(Drupal.t('Saving'))
+            .attr('disabled', 'disabled');
+          break;
+
+        default:
+          $button.attr('aria-hidden', true);
+          break;
+      }
+
+      return this;
+    },
+
+    /**
+     * @inheritdoc
+     */
+    remove: function () {
+      // Remove additional DOM elements controlled by this View.
+      this.$fence.remove();
+
+      // Stop listening to additional events.
+      $(window).off('resize.quickedit scroll.quickedit drupalViewportOffsetChange.quickedit');
+      $(document).off('drupalViewportOffsetChange.quickedit');
+
+      Backbone.View.prototype.remove.call(this);
+    },
+
+    /**
+     * Repositions the entity toolbar on window scroll and resize.
+     *
+     * @param {jQuery.Event} event
+     *   The scroll or resize event.
+     */
+    windowChangeHandler: function (event) {
+      this.position();
+    },
+
+    /**
+     * Determines the actions to take given a change of state.
+     *
+     * @param {Drupal.quickedit.FieldModel} model
+     *   The `FieldModel` model.
+     * @param {string} state
+     *   The state of the associated field. One of
+     *   {@link Drupal.quickedit.FieldModel.states}.
+     */
+    fieldStateChange: function (model, state) {
+      switch (state) {
+        case 'active':
+          this.render();
+          break;
+
+        case 'invalid':
+          this.render();
+          break;
+      }
+    },
+
+    /**
+     * Uses the jQuery.ui.position() method to position the entity toolbar.
+     *
+     * @param {HTMLElement} [element]
+     *   The element against which the entity toolbar is positioned.
+     */
+    position: function (element) {
+      clearTimeout(this.timer);
+
+      var that = this;
+      // Vary the edge of the positioning according to the direction of language
+      // in the document.
+      var edge = (document.documentElement.dir === 'rtl') ? 'right' : 'left';
+      // A time unit to wait until the entity toolbar is repositioned.
+      var delay = 0;
+      // Determines what check in the series of checks below should be
+      // evaluated.
+      var check = 0;
+      // When positioned against an active field that has padding, we should
+      // ignore that padding when positioning the toolbar, to not unnecessarily
+      // move the toolbar horizontally, which feels annoying.
+      var horizontalPadding = 0;
+      var of;
+      var activeField;
+      var highlightedField;
+      // There are several elements in the page that the entity toolbar might be
+      // positioned against. They are considered below in a priority order.
+      do {
+        switch (check) {
+          case 0:
+            // Position against a specific element.
+            of = element;
+            break;
+
+          case 1:
+            // Position against a form container.
+            activeField = Drupal.quickedit.app.model.get('activeField');
+            of = activeField && activeField.editorView && activeField.editorView.$formContainer && activeField.editorView.$formContainer.find('.quickedit-form');
+            break;
+
+          case 2:
+            // Position against an active field.
+            of = activeField && activeField.editorView && activeField.editorView.getEditedElement();
+            if (activeField && activeField.editorView && activeField.editorView.getQuickEditUISettings().padding) {
+              horizontalPadding = 5;
+            }
+            break;
+
+          case 3:
+            // Position against a highlighted field.
+            highlightedField = Drupal.quickedit.app.model.get('highlightedField');
+            of = highlightedField && highlightedField.editorView && highlightedField.editorView.getEditedElement();
+            delay = 250;
+            break;
+
+          default:
+            var fieldModels = this.model.get('fields').models;
+            var topMostPosition = 1000000;
+            var topMostField = null;
+            // Position against the topmost field.
+            for (var i = 0; i < fieldModels.length; i++) {
+              var pos = fieldModels[i].get('el').getBoundingClientRect().top;
+              if (pos < topMostPosition) {
+                topMostPosition = pos;
+                topMostField = fieldModels[i];
+              }
+            }
+            of = topMostField.get('el');
+            delay = 50;
+            break;
+        }
+        // Prepare to check the next possible element to position against.
+        check++;
+      } while (!of);
+
+      /**
+       * Refines the positioning algorithm of jquery.ui.position().
+       *
+       * Invoked as the 'using' callback of jquery.ui.position() in
+       * positionToolbar().
+       *
+       * @param {*} view
+       *   The view the positions will be calculated from.
+       * @param {object} suggested
+       *   A hash of top and left values for the position that should be set. It
+       *   can be forwarded to .css() or .animate().
+       * @param {object} info
+       *   The position and dimensions of both the 'my' element and the 'of'
+       *   elements, as well as calculations to their relative position. This
+       *   object contains the following properties:
+       * @param {object} info.element
+       *   A hash that contains information about the HTML element that will be
+       *   positioned. Also known as the 'my' element.
+       * @param {object} info.target
+       *   A hash that contains information about the HTML element that the
+       *   'my' element will be positioned against. Also known as the 'of'
+       *   element.
+       */
+      function refinePosition(view, suggested, info) {
+        // Determine if the pointer should be on the top or bottom.
+        var isBelow = suggested.top > info.target.top;
+        info.element.element.toggleClass('quickedit-toolbar-pointer-top', isBelow);
+        // Don't position the toolbar past the first or last editable field if
+        // the entity is the target.
+        if (view.$entity[0] === info.target.element[0]) {
+          // Get the first or last field according to whether the toolbar is
+          // above or below the entity.
+          var $field = view.$entity.find('.quickedit-editable').eq((isBelow) ? -1 : 0);
+          if ($field.length > 0) {
+            suggested.top = (isBelow) ? ($field.offset().top + $field.outerHeight(true)) : $field.offset().top - info.element.element.outerHeight(true);
+          }
+        }
+        // Don't let the toolbar go outside the fence.
+        var fenceTop = view.$fence.offset().top;
+        var fenceHeight = view.$fence.height();
+        var toolbarHeight = info.element.element.outerHeight(true);
+        if (suggested.top < fenceTop) {
+          suggested.top = fenceTop;
+        }
+        else if ((suggested.top + toolbarHeight) > (fenceTop + fenceHeight)) {
+          suggested.top = fenceTop + fenceHeight - toolbarHeight;
+        }
+        // Position the toolbar.
+        info.element.element.css({
+          left: Math.floor(suggested.left),
+          top: Math.floor(suggested.top)
+        });
+      }
+
+      /**
+       * Calls the jquery.ui.position() method on the $el of this view.
+       */
+      function positionToolbar() {
+        that.$el
+          .position({
+            my: edge + ' bottom',
+            // Move the toolbar 1px towards the start edge of the 'of' element,
+            // plus any horizontal padding that may have been added to the
+            // element that is being added, to prevent unwanted horizontal
+            // movement.
+            at: edge + '+' + (1 + horizontalPadding) + ' top',
+            of: of,
+            collision: 'flipfit',
+            using: refinePosition.bind(null, that),
+            within: that.$fence
+          })
+          // Resize the toolbar to match the dimensions of the field, up to a
+          // maximum width that is equal to 90% of the field's width.
+          .css({
+            'max-width': (document.documentElement.clientWidth < 450) ? document.documentElement.clientWidth : 450,
+            // Set a minimum width of 240px for the entity toolbar, or the width
+            // of the client if it is less than 240px, so that the toolbar
+            // never folds up into a squashed and jumbled mess.
+            'min-width': (document.documentElement.clientWidth < 240) ? document.documentElement.clientWidth : 240,
+            'width': '100%'
+          });
+      }
+
+      // Uses the jQuery.ui.position() method. Use a timeout to move the toolbar
+      // only after the user has focused on an editable for 250ms. This prevents
+      // the toolbar from jumping around the screen.
+      this.timer = setTimeout(function () {
+        // Render the position in the next execution cycle, so that animations
+        // on the field have time to process. This is not strictly speaking, a
+        // guarantee that all animations will be finished, but it's a simple
+        // way to get better positioning without too much additional code.
+        _.defer(positionToolbar);
+      }, delay);
+    },
+
+    /**
+     * Set the model state to 'saving' when the save button is clicked.
+     *
+     * @param {jQuery.Event} event
+     *   The click event.
+     */
+    onClickSave: function (event) {
+      event.stopPropagation();
+      event.preventDefault();
+      // Save the model.
+      this.model.set('state', 'committing');
+    },
+
+    /**
+     * Sets the model state to candidate when the cancel button is clicked.
+     *
+     * @param {jQuery.Event} event
+     *   The click event.
+     */
+    onClickCancel: function (event) {
+      event.preventDefault();
+      this.model.set('state', 'deactivating');
+    },
+
+    /**
+     * Clears the timeout that will eventually reposition the entity toolbar.
+     *
+     * Without this, it may reposition itself, away from the user's cursor!
+     *
+     * @param {jQuery.Event} event
+     *   The mouse event.
+     */
+    onMouseenter: function (event) {
+      clearTimeout(this.timer);
+    },
+
+    /**
+     * Builds the entity toolbar HTML; attaches to DOM; sets starting position.
+     *
+     * @return {jQuery}
+     *   The toolbar element.
+     */
+    buildToolbarEl: function () {
+      var $toolbar = $(Drupal.theme('quickeditEntityToolbar', {
+        id: 'quickedit-entity-toolbar'
+      }));
+
+      $toolbar
+        .find('.quickedit-toolbar-entity')
+        // Append the "ops" toolgroup into the toolbar.
+        .prepend(Drupal.theme('quickeditToolgroup', {
+          classes: ['ops'],
+          buttons: [
+            {
+              label: Drupal.t('Save'),
+              type: 'submit',
+              classes: 'action-save quickedit-button icon',
+              attributes: {
+                'aria-hidden': true
+              }
+            },
+            {
+              label: Drupal.t('Close'),
+              classes: 'action-cancel quickedit-button icon icon-close icon-only'
+            }
+          ]
+        }));
+
+      // Give the toolbar a sensible starting position so that it doesn't
+      // animate on to the screen from a far off corner.
+      $toolbar
+        .css({
+          left: this.$entity.offset().left,
+          top: this.$entity.offset().top
+        });
+
+      return $toolbar;
+    },
+
+    /**
+     * Returns the DOM element that fields will attach their toolbars to.
+     *
+     * @return {jQuery}
+     *   The DOM element that fields will attach their toolbars to.
+     */
+    getToolbarRoot: function () {
+      return this._fieldToolbarRoot;
+    },
+
+    /**
+     * Generates a state-dependent label for the entity toolbar.
+     */
+    label: function () {
+      // The entity label.
+      var label = '';
+      var entityLabel = this.model.get('label');
+
+      // Label of an active field, if it exists.
+      var activeField = Drupal.quickedit.app.model.get('activeField');
+      var activeFieldLabel = activeField && activeField.get('metadata').label;
+      // Label of a highlighted field, if it exists.
+      var highlightedField = Drupal.quickedit.app.model.get('highlightedField');
+      var highlightedFieldLabel = highlightedField && highlightedField.get('metadata').label;
+      // The label is constructed in a priority order.
+      if (activeFieldLabel) {
+        label = Drupal.theme('quickeditEntityToolbarLabel', {
+          entityLabel: entityLabel,
+          fieldLabel: activeFieldLabel
+        });
+      }
+      else if (highlightedFieldLabel) {
+        label = Drupal.theme('quickeditEntityToolbarLabel', {
+          entityLabel: entityLabel,
+          fieldLabel: highlightedFieldLabel
+        });
+      }
+      else {
+        // @todo Add XSS regression test coverage in https://www.drupal.org/node/2547437
+        label = Drupal.checkPlain(entityLabel);
+      }
+
+      this.$el
+        .find('.quickedit-toolbar-label')
+        .html(label);
+    },
+
+    /**
+     * Adds classes to a toolgroup.
+     *
+     * @param {string} toolgroup
+     *   A toolgroup name.
+     * @param {string} classes
+     *   A string of space-delimited class names that will be applied to the
+     *   wrapping element of the toolbar group.
+     */
+    addClass: function (toolgroup, classes) {
+      this._find(toolgroup).addClass(classes);
+    },
+
+    /**
+     * Removes classes from a toolgroup.
+     *
+     * @param {string} toolgroup
+     *   A toolgroup name.
+     * @param {string} classes
+     *   A string of space-delimited class names that will be removed from the
+     *   wrapping element of the toolbar group.
+     */
+    removeClass: function (toolgroup, classes) {
+      this._find(toolgroup).removeClass(classes);
+    },
+
+    /**
+     * Finds a toolgroup.
+     *
+     * @param {string} toolgroup
+     *   A toolgroup name.
+     *
+     * @return {jQuery}
+     *   The toolgroup DOM element.
+     */
+    _find: function (toolgroup) {
+      return this.$el.find('.quickedit-toolbar .quickedit-toolgroup.' + toolgroup);
+    },
+
+    /**
+     * Shows a toolgroup.
+     *
+     * @param {string} toolgroup
+     *   A toolgroup name.
+     */
+    show: function (toolgroup) {
+      this.$el.removeClass('quickedit-animate-invisible');
+    }
+
+  });
+
+})(jQuery, _, Backbone, Drupal, Drupal.debounce);
diff --git a/core/modules/quickedit/js/views/EntityToolbarView.js b/core/modules/quickedit/js/views/EntityToolbarView.js
index 4fa0506f5382..21ca0c387eee 100644
--- a/core/modules/quickedit/js/views/EntityToolbarView.js
+++ b/core/modules/quickedit/js/views/EntityToolbarView.js
@@ -1,24 +1,19 @@
 /**
- * @file
- * A Backbone View that provides an entity level toolbar.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/quickedit/js/views/EntityToolbarView.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, _, Backbone, Drupal, debounce) {
 
   'use strict';
 
-  Drupal.quickedit.EntityToolbarView = Backbone.View.extend(/** @lends Drupal.quickedit.EntityToolbarView# */{
-
-    /**
-     * @type {jQuery}
-     */
+  Drupal.quickedit.EntityToolbarView = Backbone.View.extend({
     _fieldToolbarRoot: null,
 
-    /**
-     * @return {object}
-     *   A map of events.
-     */
-    events: function () {
+    events: function events() {
       var map = {
         'click button.action-save': 'onClickSave',
         'click button.action-cancel': 'onClickCancel',
@@ -27,102 +22,60 @@
       return map;
     },
 
-    /**
-     * @constructs
-     *
-     * @augments Backbone.View
-     *
-     * @param {object} options
-     *   Options to construct the view.
-     * @param {Drupal.quickedit.AppModel} options.appModel
-     *   A quickedit `AppModel` to use in the view.
-     */
-    initialize: function (options) {
+    initialize: function initialize(options) {
       var that = this;
       this.appModel = options.appModel;
       this.$entity = $(this.model.get('el'));
 
-      // Rerender whenever the entity state changes.
       this.listenTo(this.model, 'change:isActive change:isDirty change:state', this.render);
-      // Also rerender whenever a different field is highlighted or activated.
+
       this.listenTo(this.appModel, 'change:highlightedField change:activeField', this.render);
-      // Rerender when a field of the entity changes state.
+
       this.listenTo(this.model.get('fields'), 'change:state', this.fieldStateChange);
 
-      // Reposition the entity toolbar as the viewport and the position within
-      // the viewport changes.
       $(window).on('resize.quickedit scroll.quickedit drupalViewportOffsetChange.quickedit', debounce($.proxy(this.windowChangeHandler, this), 150));
 
-      // Adjust the fence placement within which the entity toolbar may be
-      // positioned.
       $(document).on('drupalViewportOffsetChange.quickedit', function (event, offsets) {
         if (that.$fence) {
           that.$fence.css(offsets);
         }
       });
 
-      // Set the entity toolbar DOM element as the el for this view.
       var $toolbar = this.buildToolbarEl();
       this.setElement($toolbar);
       this._fieldToolbarRoot = $toolbar.find('.quickedit-toolbar-field').get(0);
 
-      // Initial render.
       this.render();
     },
 
-    /**
-     * @inheritdoc
-     *
-     * @return {Drupal.quickedit.EntityToolbarView}
-     *   The entity toolbar view.
-     */
-    render: function () {
+    render: function render() {
       if (this.model.get('isActive')) {
-        // If the toolbar container doesn't exist, create it.
         var $body = $('body');
         if ($body.children('#quickedit-entity-toolbar').length === 0) {
           $body.append(this.$el);
         }
-        // The fence will define a area on the screen that the entity toolbar
-        // will be position within.
+
         if ($body.children('#quickedit-toolbar-fence').length === 0) {
-          this.$fence = $(Drupal.theme('quickeditEntityToolbarFence'))
-            .css(Drupal.displace())
-            .appendTo($body);
+          this.$fence = $(Drupal.theme('quickeditEntityToolbarFence')).css(Drupal.displace()).appendTo($body);
         }
-        // Adds the entity title to the toolbar.
+
         this.label();
 
-        // Show the save and cancel buttons.
         this.show('ops');
-        // If render is being called and the toolbar is already visible, just
-        // reposition it.
+
         this.position();
       }
 
-      // The save button text and state varies with the state of the entity
-      // model.
       var $button = this.$el.find('.quickedit-button.action-save');
       var isDirty = this.model.get('isDirty');
-      // Adjust the save button according to the state of the model.
+
       switch (this.model.get('state')) {
-        // Quick editing is active, but no field is being edited.
         case 'opened':
-          // The saving throbber is not managed by AJAX system. The
-          // EntityToolbarView manages this visual element.
-          $button
-            .removeClass('action-saving icon-throbber icon-end')
-            .text(Drupal.t('Save'))
-            .removeAttr('disabled')
-            .attr('aria-hidden', !isDirty);
+          $button.removeClass('action-saving icon-throbber icon-end').text(Drupal.t('Save')).removeAttr('disabled').attr('aria-hidden', !isDirty);
           break;
 
-        // The changes to the fields of the entity are being committed.
         case 'committing':
-          $button
-            .addClass('action-saving icon-throbber icon-end')
-            .text(Drupal.t('Saving'))
-            .attr('disabled', 'disabled');
+          $button.addClass('action-saving icon-throbber icon-end').text(Drupal.t('Saving')).attr('disabled', 'disabled');
           break;
 
         default:
@@ -133,40 +86,20 @@
       return this;
     },
 
-    /**
-     * @inheritdoc
-     */
-    remove: function () {
-      // Remove additional DOM elements controlled by this View.
+    remove: function remove() {
       this.$fence.remove();
 
-      // Stop listening to additional events.
       $(window).off('resize.quickedit scroll.quickedit drupalViewportOffsetChange.quickedit');
       $(document).off('drupalViewportOffsetChange.quickedit');
 
       Backbone.View.prototype.remove.call(this);
     },
 
-    /**
-     * Repositions the entity toolbar on window scroll and resize.
-     *
-     * @param {jQuery.Event} event
-     *   The scroll or resize event.
-     */
-    windowChangeHandler: function (event) {
+    windowChangeHandler: function windowChangeHandler(event) {
       this.position();
     },
 
-    /**
-     * Determines the actions to take given a change of state.
-     *
-     * @param {Drupal.quickedit.FieldModel} model
-     *   The `FieldModel` model.
-     * @param {string} state
-     *   The state of the associated field. One of
-     *   {@link Drupal.quickedit.FieldModel.states}.
-     */
-    fieldStateChange: function (model, state) {
+    fieldStateChange: function fieldStateChange(model, state) {
       switch (state) {
         case 'active':
           this.render();
@@ -178,48 +111,34 @@
       }
     },
 
-    /**
-     * Uses the jQuery.ui.position() method to position the entity toolbar.
-     *
-     * @param {HTMLElement} [element]
-     *   The element against which the entity toolbar is positioned.
-     */
-    position: function (element) {
+    position: function position(element) {
       clearTimeout(this.timer);
 
       var that = this;
-      // Vary the edge of the positioning according to the direction of language
-      // in the document.
-      var edge = (document.documentElement.dir === 'rtl') ? 'right' : 'left';
-      // A time unit to wait until the entity toolbar is repositioned.
+
+      var edge = document.documentElement.dir === 'rtl' ? 'right' : 'left';
+
       var delay = 0;
-      // Determines what check in the series of checks below should be
-      // evaluated.
+
       var check = 0;
-      // When positioned against an active field that has padding, we should
-      // ignore that padding when positioning the toolbar, to not unnecessarily
-      // move the toolbar horizontally, which feels annoying.
+
       var horizontalPadding = 0;
       var of;
       var activeField;
       var highlightedField;
-      // There are several elements in the page that the entity toolbar might be
-      // positioned against. They are considered below in a priority order.
+
       do {
         switch (check) {
           case 0:
-            // Position against a specific element.
             of = element;
             break;
 
           case 1:
-            // Position against a form container.
             activeField = Drupal.quickedit.app.model.get('activeField');
             of = activeField && activeField.editorView && activeField.editorView.$formContainer && activeField.editorView.$formContainer.find('.quickedit-form');
             break;
 
           case 2:
-            // Position against an active field.
             of = activeField && activeField.editorView && activeField.editorView.getEditedElement();
             if (activeField && activeField.editorView && activeField.editorView.getQuickEditUISettings().padding) {
               horizontalPadding = 5;
@@ -227,7 +146,6 @@
             break;
 
           case 3:
-            // Position against a highlighted field.
             highlightedField = Drupal.quickedit.app.model.get('highlightedField');
             of = highlightedField && highlightedField.editorView && highlightedField.editorView.getEditedElement();
             delay = 250;
@@ -237,7 +155,7 @@
             var fieldModels = this.model.get('fields').models;
             var topMostPosition = 1000000;
             var topMostField = null;
-            // Position against the topmost field.
+
             for (var i = 0; i < fieldModels.length; i++) {
               var pos = fieldModels[i].get('el').getBoundingClientRect().top;
               if (pos < topMostPosition) {
@@ -249,280 +167,148 @@
             delay = 50;
             break;
         }
-        // Prepare to check the next possible element to position against.
+
         check++;
       } while (!of);
 
-      /**
-       * Refines the positioning algorithm of jquery.ui.position().
-       *
-       * Invoked as the 'using' callback of jquery.ui.position() in
-       * positionToolbar().
-       *
-       * @param {*} view
-       *   The view the positions will be calculated from.
-       * @param {object} suggested
-       *   A hash of top and left values for the position that should be set. It
-       *   can be forwarded to .css() or .animate().
-       * @param {object} info
-       *   The position and dimensions of both the 'my' element and the 'of'
-       *   elements, as well as calculations to their relative position. This
-       *   object contains the following properties:
-       * @param {object} info.element
-       *   A hash that contains information about the HTML element that will be
-       *   positioned. Also known as the 'my' element.
-       * @param {object} info.target
-       *   A hash that contains information about the HTML element that the
-       *   'my' element will be positioned against. Also known as the 'of'
-       *   element.
-       */
       function refinePosition(view, suggested, info) {
-        // Determine if the pointer should be on the top or bottom.
         var isBelow = suggested.top > info.target.top;
         info.element.element.toggleClass('quickedit-toolbar-pointer-top', isBelow);
-        // Don't position the toolbar past the first or last editable field if
-        // the entity is the target.
+
         if (view.$entity[0] === info.target.element[0]) {
-          // Get the first or last field according to whether the toolbar is
-          // above or below the entity.
-          var $field = view.$entity.find('.quickedit-editable').eq((isBelow) ? -1 : 0);
+          var $field = view.$entity.find('.quickedit-editable').eq(isBelow ? -1 : 0);
           if ($field.length > 0) {
-            suggested.top = (isBelow) ? ($field.offset().top + $field.outerHeight(true)) : $field.offset().top - info.element.element.outerHeight(true);
+            suggested.top = isBelow ? $field.offset().top + $field.outerHeight(true) : $field.offset().top - info.element.element.outerHeight(true);
           }
         }
-        // Don't let the toolbar go outside the fence.
+
         var fenceTop = view.$fence.offset().top;
         var fenceHeight = view.$fence.height();
         var toolbarHeight = info.element.element.outerHeight(true);
         if (suggested.top < fenceTop) {
           suggested.top = fenceTop;
-        }
-        else if ((suggested.top + toolbarHeight) > (fenceTop + fenceHeight)) {
+        } else if (suggested.top + toolbarHeight > fenceTop + fenceHeight) {
           suggested.top = fenceTop + fenceHeight - toolbarHeight;
         }
-        // Position the toolbar.
+
         info.element.element.css({
           left: Math.floor(suggested.left),
           top: Math.floor(suggested.top)
         });
       }
 
-      /**
-       * Calls the jquery.ui.position() method on the $el of this view.
-       */
       function positionToolbar() {
-        that.$el
-          .position({
-            my: edge + ' bottom',
-            // Move the toolbar 1px towards the start edge of the 'of' element,
-            // plus any horizontal padding that may have been added to the
-            // element that is being added, to prevent unwanted horizontal
-            // movement.
-            at: edge + '+' + (1 + horizontalPadding) + ' top',
-            of: of,
-            collision: 'flipfit',
-            using: refinePosition.bind(null, that),
-            within: that.$fence
-          })
-          // Resize the toolbar to match the dimensions of the field, up to a
-          // maximum width that is equal to 90% of the field's width.
-          .css({
-            'max-width': (document.documentElement.clientWidth < 450) ? document.documentElement.clientWidth : 450,
-            // Set a minimum width of 240px for the entity toolbar, or the width
-            // of the client if it is less than 240px, so that the toolbar
-            // never folds up into a squashed and jumbled mess.
-            'min-width': (document.documentElement.clientWidth < 240) ? document.documentElement.clientWidth : 240,
-            'width': '100%'
-          });
+        that.$el.position({
+          my: edge + ' bottom',
+
+          at: edge + '+' + (1 + horizontalPadding) + ' top',
+          of: of,
+          collision: 'flipfit',
+          using: refinePosition.bind(null, that),
+          within: that.$fence
+        }).css({
+          'max-width': document.documentElement.clientWidth < 450 ? document.documentElement.clientWidth : 450,
+
+          'min-width': document.documentElement.clientWidth < 240 ? document.documentElement.clientWidth : 240,
+          'width': '100%'
+        });
       }
 
-      // Uses the jQuery.ui.position() method. Use a timeout to move the toolbar
-      // only after the user has focused on an editable for 250ms. This prevents
-      // the toolbar from jumping around the screen.
       this.timer = setTimeout(function () {
-        // Render the position in the next execution cycle, so that animations
-        // on the field have time to process. This is not strictly speaking, a
-        // guarantee that all animations will be finished, but it's a simple
-        // way to get better positioning without too much additional code.
         _.defer(positionToolbar);
       }, delay);
     },
 
-    /**
-     * Set the model state to 'saving' when the save button is clicked.
-     *
-     * @param {jQuery.Event} event
-     *   The click event.
-     */
-    onClickSave: function (event) {
+    onClickSave: function onClickSave(event) {
       event.stopPropagation();
       event.preventDefault();
-      // Save the model.
+
       this.model.set('state', 'committing');
     },
 
-    /**
-     * Sets the model state to candidate when the cancel button is clicked.
-     *
-     * @param {jQuery.Event} event
-     *   The click event.
-     */
-    onClickCancel: function (event) {
+    onClickCancel: function onClickCancel(event) {
       event.preventDefault();
       this.model.set('state', 'deactivating');
     },
 
-    /**
-     * Clears the timeout that will eventually reposition the entity toolbar.
-     *
-     * Without this, it may reposition itself, away from the user's cursor!
-     *
-     * @param {jQuery.Event} event
-     *   The mouse event.
-     */
-    onMouseenter: function (event) {
+    onMouseenter: function onMouseenter(event) {
       clearTimeout(this.timer);
     },
 
-    /**
-     * Builds the entity toolbar HTML; attaches to DOM; sets starting position.
-     *
-     * @return {jQuery}
-     *   The toolbar element.
-     */
-    buildToolbarEl: function () {
+    buildToolbarEl: function buildToolbarEl() {
       var $toolbar = $(Drupal.theme('quickeditEntityToolbar', {
         id: 'quickedit-entity-toolbar'
       }));
 
-      $toolbar
-        .find('.quickedit-toolbar-entity')
-        // Append the "ops" toolgroup into the toolbar.
-        .prepend(Drupal.theme('quickeditToolgroup', {
-          classes: ['ops'],
-          buttons: [
-            {
-              label: Drupal.t('Save'),
-              type: 'submit',
-              classes: 'action-save quickedit-button icon',
-              attributes: {
-                'aria-hidden': true
-              }
-            },
-            {
-              label: Drupal.t('Close'),
-              classes: 'action-cancel quickedit-button icon icon-close icon-only'
-            }
-          ]
-        }));
-
-      // Give the toolbar a sensible starting position so that it doesn't
-      // animate on to the screen from a far off corner.
-      $toolbar
-        .css({
-          left: this.$entity.offset().left,
-          top: this.$entity.offset().top
-        });
+      $toolbar.find('.quickedit-toolbar-entity').prepend(Drupal.theme('quickeditToolgroup', {
+        classes: ['ops'],
+        buttons: [{
+          label: Drupal.t('Save'),
+          type: 'submit',
+          classes: 'action-save quickedit-button icon',
+          attributes: {
+            'aria-hidden': true
+          }
+        }, {
+          label: Drupal.t('Close'),
+          classes: 'action-cancel quickedit-button icon icon-close icon-only'
+        }]
+      }));
+
+      $toolbar.css({
+        left: this.$entity.offset().left,
+        top: this.$entity.offset().top
+      });
 
       return $toolbar;
     },
 
-    /**
-     * Returns the DOM element that fields will attach their toolbars to.
-     *
-     * @return {jQuery}
-     *   The DOM element that fields will attach their toolbars to.
-     */
-    getToolbarRoot: function () {
+    getToolbarRoot: function getToolbarRoot() {
       return this._fieldToolbarRoot;
     },
 
-    /**
-     * Generates a state-dependent label for the entity toolbar.
-     */
-    label: function () {
-      // The entity label.
+    label: function label() {
       var label = '';
       var entityLabel = this.model.get('label');
 
-      // Label of an active field, if it exists.
       var activeField = Drupal.quickedit.app.model.get('activeField');
       var activeFieldLabel = activeField && activeField.get('metadata').label;
-      // Label of a highlighted field, if it exists.
+
       var highlightedField = Drupal.quickedit.app.model.get('highlightedField');
       var highlightedFieldLabel = highlightedField && highlightedField.get('metadata').label;
-      // The label is constructed in a priority order.
+
       if (activeFieldLabel) {
         label = Drupal.theme('quickeditEntityToolbarLabel', {
           entityLabel: entityLabel,
           fieldLabel: activeFieldLabel
         });
-      }
-      else if (highlightedFieldLabel) {
+      } else if (highlightedFieldLabel) {
         label = Drupal.theme('quickeditEntityToolbarLabel', {
           entityLabel: entityLabel,
           fieldLabel: highlightedFieldLabel
         });
-      }
-      else {
-        // @todo Add XSS regression test coverage in https://www.drupal.org/node/2547437
+      } else {
         label = Drupal.checkPlain(entityLabel);
       }
 
-      this.$el
-        .find('.quickedit-toolbar-label')
-        .html(label);
+      this.$el.find('.quickedit-toolbar-label').html(label);
     },
 
-    /**
-     * Adds classes to a toolgroup.
-     *
-     * @param {string} toolgroup
-     *   A toolgroup name.
-     * @param {string} classes
-     *   A string of space-delimited class names that will be applied to the
-     *   wrapping element of the toolbar group.
-     */
-    addClass: function (toolgroup, classes) {
+    addClass: function addClass(toolgroup, classes) {
       this._find(toolgroup).addClass(classes);
     },
 
-    /**
-     * Removes classes from a toolgroup.
-     *
-     * @param {string} toolgroup
-     *   A toolgroup name.
-     * @param {string} classes
-     *   A string of space-delimited class names that will be removed from the
-     *   wrapping element of the toolbar group.
-     */
-    removeClass: function (toolgroup, classes) {
+    removeClass: function removeClass(toolgroup, classes) {
       this._find(toolgroup).removeClass(classes);
     },
 
-    /**
-     * Finds a toolgroup.
-     *
-     * @param {string} toolgroup
-     *   A toolgroup name.
-     *
-     * @return {jQuery}
-     *   The toolgroup DOM element.
-     */
-    _find: function (toolgroup) {
+    _find: function _find(toolgroup) {
       return this.$el.find('.quickedit-toolbar .quickedit-toolgroup.' + toolgroup);
     },
 
-    /**
-     * Shows a toolgroup.
-     *
-     * @param {string} toolgroup
-     *   A toolgroup name.
-     */
-    show: function (toolgroup) {
+    show: function show(toolgroup) {
       this.$el.removeClass('quickedit-animate-invisible');
     }
 
   });
-
-})(jQuery, _, Backbone, Drupal, Drupal.debounce);
+})(jQuery, _, Backbone, Drupal, Drupal.debounce);
\ No newline at end of file
diff --git a/core/modules/quickedit/js/views/FieldDecorationView.es6.js b/core/modules/quickedit/js/views/FieldDecorationView.es6.js
new file mode 100644
index 000000000000..966e2b9fbc7e
--- /dev/null
+++ b/core/modules/quickedit/js/views/FieldDecorationView.es6.js
@@ -0,0 +1,360 @@
+/**
+ * @file
+ * A Backbone View that decorates the in-place edited element.
+ */
+
+(function ($, Backbone, Drupal) {
+
+  'use strict';
+
+  Drupal.quickedit.FieldDecorationView = Backbone.View.extend(/** @lends Drupal.quickedit.FieldDecorationView# */{
+
+    /**
+     * @type {null}
+     */
+    _widthAttributeIsEmpty: null,
+
+    /**
+     * @type {object}
+     */
+    events: {
+      'mouseenter.quickedit': 'onMouseEnter',
+      'mouseleave.quickedit': 'onMouseLeave',
+      'click': 'onClick',
+      'tabIn.quickedit': 'onMouseEnter',
+      'tabOut.quickedit': 'onMouseLeave'
+    },
+
+    /**
+     * @constructs
+     *
+     * @augments Backbone.View
+     *
+     * @param {object} options
+     *   An object with the following keys:
+     * @param {Drupal.quickedit.EditorView} options.editorView
+     *   The editor object view.
+     */
+    initialize: function (options) {
+      this.editorView = options.editorView;
+
+      this.listenTo(this.model, 'change:state', this.stateChange);
+      this.listenTo(this.model, 'change:isChanged change:inTempStore', this.renderChanged);
+    },
+
+    /**
+     * @inheritdoc
+     */
+    remove: function () {
+      // The el property is the field, which should not be removed. Remove the
+      // pointer to it, then call Backbone.View.prototype.remove().
+      this.setElement();
+      Backbone.View.prototype.remove.call(this);
+    },
+
+    /**
+     * Determines the actions to take given a change of state.
+     *
+     * @param {Drupal.quickedit.FieldModel} model
+     *   The `FieldModel` model.
+     * @param {string} state
+     *   The state of the associated field. One of
+     *   {@link Drupal.quickedit.FieldModel.states}.
+     */
+    stateChange: function (model, state) {
+      var from = model.previous('state');
+      var to = state;
+      switch (to) {
+        case 'inactive':
+          this.undecorate();
+          break;
+
+        case 'candidate':
+          this.decorate();
+          if (from !== 'inactive') {
+            this.stopHighlight();
+            if (from !== 'highlighted') {
+              this.model.set('isChanged', false);
+              this.stopEdit();
+            }
+          }
+          this._unpad();
+          break;
+
+        case 'highlighted':
+          this.startHighlight();
+          break;
+
+        case 'activating':
+          // NOTE: this state is not used by every editor! It's only used by
+          // those that need to interact with the server.
+          this.prepareEdit();
+          break;
+
+        case 'active':
+          if (from !== 'activating') {
+            this.prepareEdit();
+          }
+          if (this.editorView.getQuickEditUISettings().padding) {
+            this._pad();
+          }
+          break;
+
+        case 'changed':
+          this.model.set('isChanged', true);
+          break;
+
+        case 'saving':
+          break;
+
+        case 'saved':
+          break;
+
+        case 'invalid':
+          break;
+      }
+    },
+
+    /**
+     * Adds a class to the edited element that indicates whether the field has
+     * been changed by the user (i.e. locally) or the field has already been
+     * changed and stored before by the user (i.e. remotely, stored in
+     * PrivateTempStore).
+     */
+    renderChanged: function () {
+      this.$el.toggleClass('quickedit-changed', this.model.get('isChanged') || this.model.get('inTempStore'));
+    },
+
+    /**
+     * Starts hover; transitions to 'highlight' state.
+     *
+     * @param {jQuery.Event} event
+     *   The mouse event.
+     */
+    onMouseEnter: function (event) {
+      var that = this;
+      that.model.set('state', 'highlighted');
+      event.stopPropagation();
+    },
+
+    /**
+     * Stops hover; transitions to 'candidate' state.
+     *
+     * @param {jQuery.Event} event
+     *   The mouse event.
+     */
+    onMouseLeave: function (event) {
+      var that = this;
+      that.model.set('state', 'candidate', {reason: 'mouseleave'});
+      event.stopPropagation();
+    },
+
+    /**
+     * Transition to 'activating' stage.
+     *
+     * @param {jQuery.Event} event
+     *   The click event.
+     */
+    onClick: function (event) {
+      this.model.set('state', 'activating');
+      event.preventDefault();
+      event.stopPropagation();
+    },
+
+    /**
+     * Adds classes used to indicate an elements editable state.
+     */
+    decorate: function () {
+      this.$el.addClass('quickedit-candidate quickedit-editable');
+    },
+
+    /**
+     * Removes classes used to indicate an elements editable state.
+     */
+    undecorate: function () {
+      this.$el.removeClass('quickedit-candidate quickedit-editable quickedit-highlighted quickedit-editing');
+    },
+
+    /**
+     * Adds that class that indicates that an element is highlighted.
+     */
+    startHighlight: function () {
+      // Animations.
+      var that = this;
+      // Use a timeout to grab the next available animation frame.
+      that.$el.addClass('quickedit-highlighted');
+    },
+
+    /**
+     * Removes the class that indicates that an element is highlighted.
+     */
+    stopHighlight: function () {
+      this.$el.removeClass('quickedit-highlighted');
+    },
+
+    /**
+     * Removes the class that indicates that an element as editable.
+     */
+    prepareEdit: function () {
+      this.$el.addClass('quickedit-editing');
+
+      // Allow the field to be styled differently while editing in a pop-up
+      // in-place editor.
+      if (this.editorView.getQuickEditUISettings().popup) {
+        this.$el.addClass('quickedit-editor-is-popup');
+      }
+    },
+
+    /**
+     * Removes the class that indicates that an element is being edited.
+     *
+     * Reapplies the class that indicates that a candidate editable element is
+     * again available to be edited.
+     */
+    stopEdit: function () {
+      this.$el.removeClass('quickedit-highlighted quickedit-editing');
+
+      // Done editing in a pop-up in-place editor; remove the class.
+      if (this.editorView.getQuickEditUISettings().popup) {
+        this.$el.removeClass('quickedit-editor-is-popup');
+      }
+
+      // Make the other editors show up again.
+      $('.quickedit-candidate').addClass('quickedit-editable');
+    },
+
+    /**
+     * Adds padding around the editable element to make it pop visually.
+     */
+    _pad: function () {
+      // Early return if the element has already been padded.
+      if (this.$el.data('quickedit-padded')) {
+        return;
+      }
+      var self = this;
+
+      // Add 5px padding for readability. This means we'll freeze the current
+      // width and *then* add 5px padding, hence ensuring the padding is added
+      // "on the outside".
+      // 1) Freeze the width (if it's not already set); don't use animations.
+      if (this.$el[0].style.width === '') {
+        this._widthAttributeIsEmpty = true;
+        this.$el
+          .addClass('quickedit-animate-disable-width')
+          .css('width', this.$el.width());
+      }
+
+      // 2) Add padding; use animations.
+      var posProp = this._getPositionProperties(this.$el);
+      setTimeout(function () {
+        // Re-enable width animations (padding changes affect width too!).
+        self.$el.removeClass('quickedit-animate-disable-width');
+
+        // Pad the editable.
+        self.$el
+          .css({
+            'position': 'relative',
+            'top': posProp.top - 5 + 'px',
+            'left': posProp.left - 5 + 'px',
+            'padding-top': posProp['padding-top'] + 5 + 'px',
+            'padding-left': posProp['padding-left'] + 5 + 'px',
+            'padding-right': posProp['padding-right'] + 5 + 'px',
+            'padding-bottom': posProp['padding-bottom'] + 5 + 'px',
+            'margin-bottom': posProp['margin-bottom'] - 10 + 'px'
+          })
+          .data('quickedit-padded', true);
+      }, 0);
+    },
+
+    /**
+     * Removes the padding around the element being edited when editing ceases.
+     */
+    _unpad: function () {
+      // Early return if the element has not been padded.
+      if (!this.$el.data('quickedit-padded')) {
+        return;
+      }
+      var self = this;
+
+      // 1) Set the empty width again.
+      if (this._widthAttributeIsEmpty) {
+        this.$el
+          .addClass('quickedit-animate-disable-width')
+          .css('width', '');
+      }
+
+      // 2) Remove padding; use animations (these will run simultaneously with)
+      // the fading out of the toolbar as its gets removed).
+      var posProp = this._getPositionProperties(this.$el);
+      setTimeout(function () {
+        // Re-enable width animations (padding changes affect width too!).
+        self.$el.removeClass('quickedit-animate-disable-width');
+
+        // Unpad the editable.
+        self.$el
+          .css({
+            'position': 'relative',
+            'top': posProp.top + 5 + 'px',
+            'left': posProp.left + 5 + 'px',
+            'padding-top': posProp['padding-top'] - 5 + 'px',
+            'padding-left': posProp['padding-left'] - 5 + 'px',
+            'padding-right': posProp['padding-right'] - 5 + 'px',
+            'padding-bottom': posProp['padding-bottom'] - 5 + 'px',
+            'margin-bottom': posProp['margin-bottom'] + 10 + 'px'
+          });
+      }, 0);
+      // Remove the marker that indicates that this field has padding. This is
+      // done outside the timed out function above so that we don't get numerous
+      // queued functions that will remove padding before the data marker has
+      // been removed.
+      this.$el.removeData('quickedit-padded');
+    },
+
+    /**
+     * Gets the top and left properties of an element.
+     *
+     * Convert extraneous values and information into numbers ready for
+     * subtraction.
+     *
+     * @param {jQuery} $e
+     *   The element to get position properties from.
+     *
+     * @return {object}
+     *   An object containing css values for the needed properties.
+     */
+    _getPositionProperties: function ($e) {
+      var p;
+      var r = {};
+      var props = [
+        'top', 'left', 'bottom', 'right',
+        'padding-top', 'padding-left', 'padding-right', 'padding-bottom',
+        'margin-bottom'
+      ];
+
+      var propCount = props.length;
+      for (var i = 0; i < propCount; i++) {
+        p = props[i];
+        r[p] = parseInt(this._replaceBlankPosition($e.css(p)), 10);
+      }
+      return r;
+    },
+
+    /**
+     * Replaces blank or 'auto' CSS `position: <value>` values with "0px".
+     *
+     * @param {string} [pos]
+     *   The value for a CSS position declaration.
+     *
+     * @return {string}
+     *   A CSS value that is valid for `position`.
+     */
+    _replaceBlankPosition: function (pos) {
+      if (pos === 'auto' || !pos) {
+        pos = '0px';
+      }
+      return pos;
+    }
+
+  });
+
+})(jQuery, Backbone, Drupal);
diff --git a/core/modules/quickedit/js/views/FieldDecorationView.js b/core/modules/quickedit/js/views/FieldDecorationView.js
index 966e2b9fbc7e..cfe175d5cca7 100644
--- a/core/modules/quickedit/js/views/FieldDecorationView.js
+++ b/core/modules/quickedit/js/views/FieldDecorationView.js
@@ -1,22 +1,18 @@
 /**
- * @file
- * A Backbone View that decorates the in-place edited element.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/quickedit/js/views/FieldDecorationView.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Backbone, Drupal) {
 
   'use strict';
 
-  Drupal.quickedit.FieldDecorationView = Backbone.View.extend(/** @lends Drupal.quickedit.FieldDecorationView# */{
-
-    /**
-     * @type {null}
-     */
+  Drupal.quickedit.FieldDecorationView = Backbone.View.extend({
     _widthAttributeIsEmpty: null,
 
-    /**
-     * @type {object}
-     */
     events: {
       'mouseenter.quickedit': 'onMouseEnter',
       'mouseleave.quickedit': 'onMouseLeave',
@@ -25,43 +21,19 @@
       'tabOut.quickedit': 'onMouseLeave'
     },
 
-    /**
-     * @constructs
-     *
-     * @augments Backbone.View
-     *
-     * @param {object} options
-     *   An object with the following keys:
-     * @param {Drupal.quickedit.EditorView} options.editorView
-     *   The editor object view.
-     */
-    initialize: function (options) {
+    initialize: function initialize(options) {
       this.editorView = options.editorView;
 
       this.listenTo(this.model, 'change:state', this.stateChange);
       this.listenTo(this.model, 'change:isChanged change:inTempStore', this.renderChanged);
     },
 
-    /**
-     * @inheritdoc
-     */
-    remove: function () {
-      // The el property is the field, which should not be removed. Remove the
-      // pointer to it, then call Backbone.View.prototype.remove().
+    remove: function remove() {
       this.setElement();
       Backbone.View.prototype.remove.call(this);
     },
 
-    /**
-     * Determines the actions to take given a change of state.
-     *
-     * @param {Drupal.quickedit.FieldModel} model
-     *   The `FieldModel` model.
-     * @param {string} state
-     *   The state of the associated field. One of
-     *   {@link Drupal.quickedit.FieldModel.states}.
-     */
-    stateChange: function (model, state) {
+    stateChange: function stateChange(model, state) {
       var from = model.previous('state');
       var to = state;
       switch (to) {
@@ -86,8 +58,6 @@
           break;
 
         case 'activating':
-          // NOTE: this state is not used by every editor! It's only used by
-          // those that need to interact with the server.
           this.prepareEdit();
           break;
 
@@ -115,221 +85,125 @@
       }
     },
 
-    /**
-     * Adds a class to the edited element that indicates whether the field has
-     * been changed by the user (i.e. locally) or the field has already been
-     * changed and stored before by the user (i.e. remotely, stored in
-     * PrivateTempStore).
-     */
-    renderChanged: function () {
+    renderChanged: function renderChanged() {
       this.$el.toggleClass('quickedit-changed', this.model.get('isChanged') || this.model.get('inTempStore'));
     },
 
-    /**
-     * Starts hover; transitions to 'highlight' state.
-     *
-     * @param {jQuery.Event} event
-     *   The mouse event.
-     */
-    onMouseEnter: function (event) {
+    onMouseEnter: function onMouseEnter(event) {
       var that = this;
       that.model.set('state', 'highlighted');
       event.stopPropagation();
     },
 
-    /**
-     * Stops hover; transitions to 'candidate' state.
-     *
-     * @param {jQuery.Event} event
-     *   The mouse event.
-     */
-    onMouseLeave: function (event) {
+    onMouseLeave: function onMouseLeave(event) {
       var that = this;
-      that.model.set('state', 'candidate', {reason: 'mouseleave'});
+      that.model.set('state', 'candidate', { reason: 'mouseleave' });
       event.stopPropagation();
     },
 
-    /**
-     * Transition to 'activating' stage.
-     *
-     * @param {jQuery.Event} event
-     *   The click event.
-     */
-    onClick: function (event) {
+    onClick: function onClick(event) {
       this.model.set('state', 'activating');
       event.preventDefault();
       event.stopPropagation();
     },
 
-    /**
-     * Adds classes used to indicate an elements editable state.
-     */
-    decorate: function () {
+    decorate: function decorate() {
       this.$el.addClass('quickedit-candidate quickedit-editable');
     },
 
-    /**
-     * Removes classes used to indicate an elements editable state.
-     */
-    undecorate: function () {
+    undecorate: function undecorate() {
       this.$el.removeClass('quickedit-candidate quickedit-editable quickedit-highlighted quickedit-editing');
     },
 
-    /**
-     * Adds that class that indicates that an element is highlighted.
-     */
-    startHighlight: function () {
-      // Animations.
+    startHighlight: function startHighlight() {
       var that = this;
-      // Use a timeout to grab the next available animation frame.
+
       that.$el.addClass('quickedit-highlighted');
     },
 
-    /**
-     * Removes the class that indicates that an element is highlighted.
-     */
-    stopHighlight: function () {
+    stopHighlight: function stopHighlight() {
       this.$el.removeClass('quickedit-highlighted');
     },
 
-    /**
-     * Removes the class that indicates that an element as editable.
-     */
-    prepareEdit: function () {
+    prepareEdit: function prepareEdit() {
       this.$el.addClass('quickedit-editing');
 
-      // Allow the field to be styled differently while editing in a pop-up
-      // in-place editor.
       if (this.editorView.getQuickEditUISettings().popup) {
         this.$el.addClass('quickedit-editor-is-popup');
       }
     },
 
-    /**
-     * Removes the class that indicates that an element is being edited.
-     *
-     * Reapplies the class that indicates that a candidate editable element is
-     * again available to be edited.
-     */
-    stopEdit: function () {
+    stopEdit: function stopEdit() {
       this.$el.removeClass('quickedit-highlighted quickedit-editing');
 
-      // Done editing in a pop-up in-place editor; remove the class.
       if (this.editorView.getQuickEditUISettings().popup) {
         this.$el.removeClass('quickedit-editor-is-popup');
       }
 
-      // Make the other editors show up again.
       $('.quickedit-candidate').addClass('quickedit-editable');
     },
 
-    /**
-     * Adds padding around the editable element to make it pop visually.
-     */
-    _pad: function () {
-      // Early return if the element has already been padded.
+    _pad: function _pad() {
       if (this.$el.data('quickedit-padded')) {
         return;
       }
       var self = this;
 
-      // Add 5px padding for readability. This means we'll freeze the current
-      // width and *then* add 5px padding, hence ensuring the padding is added
-      // "on the outside".
-      // 1) Freeze the width (if it's not already set); don't use animations.
       if (this.$el[0].style.width === '') {
         this._widthAttributeIsEmpty = true;
-        this.$el
-          .addClass('quickedit-animate-disable-width')
-          .css('width', this.$el.width());
+        this.$el.addClass('quickedit-animate-disable-width').css('width', this.$el.width());
       }
 
-      // 2) Add padding; use animations.
       var posProp = this._getPositionProperties(this.$el);
       setTimeout(function () {
-        // Re-enable width animations (padding changes affect width too!).
         self.$el.removeClass('quickedit-animate-disable-width');
 
-        // Pad the editable.
-        self.$el
-          .css({
-            'position': 'relative',
-            'top': posProp.top - 5 + 'px',
-            'left': posProp.left - 5 + 'px',
-            'padding-top': posProp['padding-top'] + 5 + 'px',
-            'padding-left': posProp['padding-left'] + 5 + 'px',
-            'padding-right': posProp['padding-right'] + 5 + 'px',
-            'padding-bottom': posProp['padding-bottom'] + 5 + 'px',
-            'margin-bottom': posProp['margin-bottom'] - 10 + 'px'
-          })
-          .data('quickedit-padded', true);
+        self.$el.css({
+          'position': 'relative',
+          'top': posProp.top - 5 + 'px',
+          'left': posProp.left - 5 + 'px',
+          'padding-top': posProp['padding-top'] + 5 + 'px',
+          'padding-left': posProp['padding-left'] + 5 + 'px',
+          'padding-right': posProp['padding-right'] + 5 + 'px',
+          'padding-bottom': posProp['padding-bottom'] + 5 + 'px',
+          'margin-bottom': posProp['margin-bottom'] - 10 + 'px'
+        }).data('quickedit-padded', true);
       }, 0);
     },
 
-    /**
-     * Removes the padding around the element being edited when editing ceases.
-     */
-    _unpad: function () {
-      // Early return if the element has not been padded.
+    _unpad: function _unpad() {
       if (!this.$el.data('quickedit-padded')) {
         return;
       }
       var self = this;
 
-      // 1) Set the empty width again.
       if (this._widthAttributeIsEmpty) {
-        this.$el
-          .addClass('quickedit-animate-disable-width')
-          .css('width', '');
+        this.$el.addClass('quickedit-animate-disable-width').css('width', '');
       }
 
-      // 2) Remove padding; use animations (these will run simultaneously with)
-      // the fading out of the toolbar as its gets removed).
       var posProp = this._getPositionProperties(this.$el);
       setTimeout(function () {
-        // Re-enable width animations (padding changes affect width too!).
         self.$el.removeClass('quickedit-animate-disable-width');
 
-        // Unpad the editable.
-        self.$el
-          .css({
-            'position': 'relative',
-            'top': posProp.top + 5 + 'px',
-            'left': posProp.left + 5 + 'px',
-            'padding-top': posProp['padding-top'] - 5 + 'px',
-            'padding-left': posProp['padding-left'] - 5 + 'px',
-            'padding-right': posProp['padding-right'] - 5 + 'px',
-            'padding-bottom': posProp['padding-bottom'] - 5 + 'px',
-            'margin-bottom': posProp['margin-bottom'] + 10 + 'px'
-          });
+        self.$el.css({
+          'position': 'relative',
+          'top': posProp.top + 5 + 'px',
+          'left': posProp.left + 5 + 'px',
+          'padding-top': posProp['padding-top'] - 5 + 'px',
+          'padding-left': posProp['padding-left'] - 5 + 'px',
+          'padding-right': posProp['padding-right'] - 5 + 'px',
+          'padding-bottom': posProp['padding-bottom'] - 5 + 'px',
+          'margin-bottom': posProp['margin-bottom'] + 10 + 'px'
+        });
       }, 0);
-      // Remove the marker that indicates that this field has padding. This is
-      // done outside the timed out function above so that we don't get numerous
-      // queued functions that will remove padding before the data marker has
-      // been removed.
+
       this.$el.removeData('quickedit-padded');
     },
 
-    /**
-     * Gets the top and left properties of an element.
-     *
-     * Convert extraneous values and information into numbers ready for
-     * subtraction.
-     *
-     * @param {jQuery} $e
-     *   The element to get position properties from.
-     *
-     * @return {object}
-     *   An object containing css values for the needed properties.
-     */
-    _getPositionProperties: function ($e) {
+    _getPositionProperties: function _getPositionProperties($e) {
       var p;
       var r = {};
-      var props = [
-        'top', 'left', 'bottom', 'right',
-        'padding-top', 'padding-left', 'padding-right', 'padding-bottom',
-        'margin-bottom'
-      ];
+      var props = ['top', 'left', 'bottom', 'right', 'padding-top', 'padding-left', 'padding-right', 'padding-bottom', 'margin-bottom'];
 
       var propCount = props.length;
       for (var i = 0; i < propCount; i++) {
@@ -339,16 +213,7 @@
       return r;
     },
 
-    /**
-     * Replaces blank or 'auto' CSS `position: <value>` values with "0px".
-     *
-     * @param {string} [pos]
-     *   The value for a CSS position declaration.
-     *
-     * @return {string}
-     *   A CSS value that is valid for `position`.
-     */
-    _replaceBlankPosition: function (pos) {
+    _replaceBlankPosition: function _replaceBlankPosition(pos) {
       if (pos === 'auto' || !pos) {
         pos = '0px';
       }
@@ -356,5 +221,4 @@
     }
 
   });
-
-})(jQuery, Backbone, Drupal);
+})(jQuery, Backbone, Drupal);
\ No newline at end of file
diff --git a/core/modules/quickedit/js/views/FieldToolbarView.es6.js b/core/modules/quickedit/js/views/FieldToolbarView.es6.js
new file mode 100644
index 000000000000..dbd531f44132
--- /dev/null
+++ b/core/modules/quickedit/js/views/FieldToolbarView.es6.js
@@ -0,0 +1,227 @@
+/**
+ * @file
+ * A Backbone View that provides an interactive toolbar (1 per in-place editor).
+ */
+
+(function ($, _, Backbone, Drupal) {
+
+  'use strict';
+
+  Drupal.quickedit.FieldToolbarView = Backbone.View.extend(/** @lends Drupal.quickedit.FieldToolbarView# */{
+
+    /**
+     * The edited element, as indicated by EditorView.getEditedElement.
+     *
+     * @type {jQuery}
+     */
+    $editedElement: null,
+
+    /**
+     * A reference to the in-place editor.
+     *
+     * @type {Drupal.quickedit.EditorView}
+     */
+    editorView: null,
+
+    /**
+     * @type {string}
+     */
+    _id: null,
+
+    /**
+     * @constructs
+     *
+     * @augments Backbone.View
+     *
+     * @param {object} options
+     *   Options object to construct the field toolbar.
+     * @param {jQuery} options.$editedElement
+     *   The element being edited.
+     * @param {Drupal.quickedit.EditorView} options.editorView
+     *   The EditorView the toolbar belongs to.
+     */
+    initialize: function (options) {
+      this.$editedElement = options.$editedElement;
+      this.editorView = options.editorView;
+
+      /**
+       * @type {jQuery}
+       */
+      this.$root = this.$el;
+
+      // Generate a DOM-compatible ID for the form container DOM element.
+      this._id = 'quickedit-toolbar-for-' + this.model.id.replace(/[\/\[\]]/g, '_');
+
+      this.listenTo(this.model, 'change:state', this.stateChange);
+    },
+
+    /**
+     * @inheritdoc
+     *
+     * @return {Drupal.quickedit.FieldToolbarView}
+     *   The current FieldToolbarView.
+     */
+    render: function () {
+      // Render toolbar and set it as the view's element.
+      this.setElement($(Drupal.theme('quickeditFieldToolbar', {
+        id: this._id
+      })));
+
+      // Attach to the field toolbar $root element in the entity toolbar.
+      this.$el.prependTo(this.$root);
+
+      return this;
+    },
+
+    /**
+     * Determines the actions to take given a change of state.
+     *
+     * @param {Drupal.quickedit.FieldModel} model
+     *   The quickedit FieldModel
+     * @param {string} state
+     *   The state of the associated field. One of
+     *   {@link Drupal.quickedit.FieldModel.states}.
+     */
+    stateChange: function (model, state) {
+      var from = model.previous('state');
+      var to = state;
+      switch (to) {
+        case 'inactive':
+          break;
+
+        case 'candidate':
+          // Remove the view's existing element if we went to the 'activating'
+          // state or later, because it will be recreated. Not doing this would
+          // result in memory leaks.
+          if (from !== 'inactive' && from !== 'highlighted') {
+            this.$el.remove();
+            this.setElement();
+          }
+          break;
+
+        case 'highlighted':
+          break;
+
+        case 'activating':
+          this.render();
+
+          if (this.editorView.getQuickEditUISettings().fullWidthToolbar) {
+            this.$el.addClass('quickedit-toolbar-fullwidth');
+          }
+
+          if (this.editorView.getQuickEditUISettings().unifiedToolbar) {
+            this.insertWYSIWYGToolGroups();
+          }
+          break;
+
+        case 'active':
+          break;
+
+        case 'changed':
+          break;
+
+        case 'saving':
+          break;
+
+        case 'saved':
+          break;
+
+        case 'invalid':
+          break;
+      }
+    },
+
+    /**
+     * Insert WYSIWYG markup into the associated toolbar.
+     */
+    insertWYSIWYGToolGroups: function () {
+      this.$el
+        .append(Drupal.theme('quickeditToolgroup', {
+          id: this.getFloatedWysiwygToolgroupId(),
+          classes: ['wysiwyg-floated', 'quickedit-animate-slow', 'quickedit-animate-invisible', 'quickedit-animate-delay-veryfast'],
+          buttons: []
+        }))
+        .append(Drupal.theme('quickeditToolgroup', {
+          id: this.getMainWysiwygToolgroupId(),
+          classes: ['wysiwyg-main', 'quickedit-animate-slow', 'quickedit-animate-invisible', 'quickedit-animate-delay-veryfast'],
+          buttons: []
+        }));
+
+      // Animate the toolgroups into visibility.
+      this.show('wysiwyg-floated');
+      this.show('wysiwyg-main');
+    },
+
+    /**
+     * Retrieves the ID for this toolbar's container.
+     *
+     * Only used to make sane hovering behavior possible.
+     *
+     * @return {string}
+     *   A string that can be used as the ID for this toolbar's container.
+     */
+    getId: function () {
+      return 'quickedit-toolbar-for-' + this._id;
+    },
+
+    /**
+     * Retrieves the ID for this toolbar's floating WYSIWYG toolgroup.
+     *
+     * Used to provide an abstraction for any WYSIWYG editor to plug in.
+     *
+     * @return {string}
+     *   A string that can be used as the ID.
+     */
+    getFloatedWysiwygToolgroupId: function () {
+      return 'quickedit-wysiwyg-floated-toolgroup-for-' + this._id;
+    },
+
+    /**
+     * Retrieves the ID for this toolbar's main WYSIWYG toolgroup.
+     *
+     * Used to provide an abstraction for any WYSIWYG editor to plug in.
+     *
+     * @return {string}
+     *   A string that can be used as the ID.
+     */
+    getMainWysiwygToolgroupId: function () {
+      return 'quickedit-wysiwyg-main-toolgroup-for-' + this._id;
+    },
+
+    /**
+     * Finds a toolgroup.
+     *
+     * @param {string} toolgroup
+     *   A toolgroup name.
+     *
+     * @return {jQuery}
+     *   The toolgroup element.
+     */
+    _find: function (toolgroup) {
+      return this.$el.find('.quickedit-toolgroup.' + toolgroup);
+    },
+
+    /**
+     * Shows a toolgroup.
+     *
+     * @param {string} toolgroup
+     *   A toolgroup name.
+     */
+    show: function (toolgroup) {
+      var $group = this._find(toolgroup);
+      // Attach a transitionEnd event handler to the toolbar group so that
+      // update events can be triggered after the animations have ended.
+      $group.on(Drupal.quickedit.util.constants.transitionEnd, function (event) {
+        $group.off(Drupal.quickedit.util.constants.transitionEnd);
+      });
+      // The call to remove the class and start the animation must be started in
+      // the next animation frame or the event handler attached above won't be
+      // triggered.
+      window.setTimeout(function () {
+        $group.removeClass('quickedit-animate-invisible');
+      }, 0);
+    }
+
+  });
+
+})(jQuery, _, Backbone, Drupal);
diff --git a/core/modules/quickedit/js/views/FieldToolbarView.js b/core/modules/quickedit/js/views/FieldToolbarView.js
index dbd531f44132..488730e15243 100644
--- a/core/modules/quickedit/js/views/FieldToolbarView.js
+++ b/core/modules/quickedit/js/views/FieldToolbarView.js
@@ -1,88 +1,44 @@
 /**
- * @file
- * A Backbone View that provides an interactive toolbar (1 per in-place editor).
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/quickedit/js/views/FieldToolbarView.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, _, Backbone, Drupal) {
 
   'use strict';
 
-  Drupal.quickedit.FieldToolbarView = Backbone.View.extend(/** @lends Drupal.quickedit.FieldToolbarView# */{
-
-    /**
-     * The edited element, as indicated by EditorView.getEditedElement.
-     *
-     * @type {jQuery}
-     */
+  Drupal.quickedit.FieldToolbarView = Backbone.View.extend({
     $editedElement: null,
 
-    /**
-     * A reference to the in-place editor.
-     *
-     * @type {Drupal.quickedit.EditorView}
-     */
     editorView: null,
 
-    /**
-     * @type {string}
-     */
     _id: null,
 
-    /**
-     * @constructs
-     *
-     * @augments Backbone.View
-     *
-     * @param {object} options
-     *   Options object to construct the field toolbar.
-     * @param {jQuery} options.$editedElement
-     *   The element being edited.
-     * @param {Drupal.quickedit.EditorView} options.editorView
-     *   The EditorView the toolbar belongs to.
-     */
-    initialize: function (options) {
+    initialize: function initialize(options) {
       this.$editedElement = options.$editedElement;
       this.editorView = options.editorView;
 
-      /**
-       * @type {jQuery}
-       */
       this.$root = this.$el;
 
-      // Generate a DOM-compatible ID for the form container DOM element.
       this._id = 'quickedit-toolbar-for-' + this.model.id.replace(/[\/\[\]]/g, '_');
 
       this.listenTo(this.model, 'change:state', this.stateChange);
     },
 
-    /**
-     * @inheritdoc
-     *
-     * @return {Drupal.quickedit.FieldToolbarView}
-     *   The current FieldToolbarView.
-     */
-    render: function () {
-      // Render toolbar and set it as the view's element.
+    render: function render() {
       this.setElement($(Drupal.theme('quickeditFieldToolbar', {
         id: this._id
       })));
 
-      // Attach to the field toolbar $root element in the entity toolbar.
       this.$el.prependTo(this.$root);
 
       return this;
     },
 
-    /**
-     * Determines the actions to take given a change of state.
-     *
-     * @param {Drupal.quickedit.FieldModel} model
-     *   The quickedit FieldModel
-     * @param {string} state
-     *   The state of the associated field. One of
-     *   {@link Drupal.quickedit.FieldModel.states}.
-     */
-    stateChange: function (model, state) {
+    stateChange: function stateChange(model, state) {
       var from = model.previous('state');
       var to = state;
       switch (to) {
@@ -90,9 +46,6 @@
           break;
 
         case 'candidate':
-          // Remove the view's existing element if we went to the 'activating'
-          // state or later, because it will be recreated. Not doing this would
-          // result in memory leaks.
           if (from !== 'inactive' && from !== 'highlighted') {
             this.$el.remove();
             this.setElement();
@@ -131,97 +84,48 @@
       }
     },
 
-    /**
-     * Insert WYSIWYG markup into the associated toolbar.
-     */
-    insertWYSIWYGToolGroups: function () {
-      this.$el
-        .append(Drupal.theme('quickeditToolgroup', {
-          id: this.getFloatedWysiwygToolgroupId(),
-          classes: ['wysiwyg-floated', 'quickedit-animate-slow', 'quickedit-animate-invisible', 'quickedit-animate-delay-veryfast'],
-          buttons: []
-        }))
-        .append(Drupal.theme('quickeditToolgroup', {
-          id: this.getMainWysiwygToolgroupId(),
-          classes: ['wysiwyg-main', 'quickedit-animate-slow', 'quickedit-animate-invisible', 'quickedit-animate-delay-veryfast'],
-          buttons: []
-        }));
-
-      // Animate the toolgroups into visibility.
+    insertWYSIWYGToolGroups: function insertWYSIWYGToolGroups() {
+      this.$el.append(Drupal.theme('quickeditToolgroup', {
+        id: this.getFloatedWysiwygToolgroupId(),
+        classes: ['wysiwyg-floated', 'quickedit-animate-slow', 'quickedit-animate-invisible', 'quickedit-animate-delay-veryfast'],
+        buttons: []
+      })).append(Drupal.theme('quickeditToolgroup', {
+        id: this.getMainWysiwygToolgroupId(),
+        classes: ['wysiwyg-main', 'quickedit-animate-slow', 'quickedit-animate-invisible', 'quickedit-animate-delay-veryfast'],
+        buttons: []
+      }));
+
       this.show('wysiwyg-floated');
       this.show('wysiwyg-main');
     },
 
-    /**
-     * Retrieves the ID for this toolbar's container.
-     *
-     * Only used to make sane hovering behavior possible.
-     *
-     * @return {string}
-     *   A string that can be used as the ID for this toolbar's container.
-     */
-    getId: function () {
+    getId: function getId() {
       return 'quickedit-toolbar-for-' + this._id;
     },
 
-    /**
-     * Retrieves the ID for this toolbar's floating WYSIWYG toolgroup.
-     *
-     * Used to provide an abstraction for any WYSIWYG editor to plug in.
-     *
-     * @return {string}
-     *   A string that can be used as the ID.
-     */
-    getFloatedWysiwygToolgroupId: function () {
+    getFloatedWysiwygToolgroupId: function getFloatedWysiwygToolgroupId() {
       return 'quickedit-wysiwyg-floated-toolgroup-for-' + this._id;
     },
 
-    /**
-     * Retrieves the ID for this toolbar's main WYSIWYG toolgroup.
-     *
-     * Used to provide an abstraction for any WYSIWYG editor to plug in.
-     *
-     * @return {string}
-     *   A string that can be used as the ID.
-     */
-    getMainWysiwygToolgroupId: function () {
+    getMainWysiwygToolgroupId: function getMainWysiwygToolgroupId() {
       return 'quickedit-wysiwyg-main-toolgroup-for-' + this._id;
     },
 
-    /**
-     * Finds a toolgroup.
-     *
-     * @param {string} toolgroup
-     *   A toolgroup name.
-     *
-     * @return {jQuery}
-     *   The toolgroup element.
-     */
-    _find: function (toolgroup) {
+    _find: function _find(toolgroup) {
       return this.$el.find('.quickedit-toolgroup.' + toolgroup);
     },
 
-    /**
-     * Shows a toolgroup.
-     *
-     * @param {string} toolgroup
-     *   A toolgroup name.
-     */
-    show: function (toolgroup) {
+    show: function show(toolgroup) {
       var $group = this._find(toolgroup);
-      // Attach a transitionEnd event handler to the toolbar group so that
-      // update events can be triggered after the animations have ended.
+
       $group.on(Drupal.quickedit.util.constants.transitionEnd, function (event) {
         $group.off(Drupal.quickedit.util.constants.transitionEnd);
       });
-      // The call to remove the class and start the animation must be started in
-      // the next animation frame or the event handler attached above won't be
-      // triggered.
+
       window.setTimeout(function () {
         $group.removeClass('quickedit-animate-invisible');
       }, 0);
     }
 
   });
-
-})(jQuery, _, Backbone, Drupal);
+})(jQuery, _, Backbone, Drupal);
\ No newline at end of file
diff --git a/core/modules/responsive_image/js/responsive_image.ajax.es6.js b/core/modules/responsive_image/js/responsive_image.ajax.es6.js
new file mode 100644
index 000000000000..52093193ec8a
--- /dev/null
+++ b/core/modules/responsive_image/js/responsive_image.ajax.es6.js
@@ -0,0 +1,16 @@
+(function (Drupal) {
+
+  'use strict';
+
+  /**
+   * Call picturefill so newly added responsive images are processed.
+   */
+  Drupal.behaviors.responsiveImageAJAX = {
+    attach: function () {
+      if (window.picturefill) {
+        window.picturefill();
+      }
+    }
+  };
+
+})(Drupal);
diff --git a/core/modules/responsive_image/js/responsive_image.ajax.js b/core/modules/responsive_image/js/responsive_image.ajax.js
index 52093193ec8a..d376e09b75d9 100644
--- a/core/modules/responsive_image/js/responsive_image.ajax.js
+++ b/core/modules/responsive_image/js/responsive_image.ajax.js
@@ -1,16 +1,20 @@
+/**
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/responsive_image/js/responsive_image.ajax.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
+
 (function (Drupal) {
 
   'use strict';
 
-  /**
-   * Call picturefill so newly added responsive images are processed.
-   */
   Drupal.behaviors.responsiveImageAJAX = {
-    attach: function () {
+    attach: function attach() {
       if (window.picturefill) {
         window.picturefill();
       }
     }
   };
-
-})(Drupal);
+})(Drupal);
\ No newline at end of file
diff --git a/core/modules/simpletest/simpletest.es6.js b/core/modules/simpletest/simpletest.es6.js
new file mode 100644
index 000000000000..ba54cbf74b35
--- /dev/null
+++ b/core/modules/simpletest/simpletest.es6.js
@@ -0,0 +1,130 @@
+/**
+ * @file
+ * Simpletest behaviors.
+ */
+
+(function ($, Drupal, drupalSettings) {
+
+  'use strict';
+
+  /**
+   * Collapses table rows followed by group rows on the test listing page.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attach collapse behavior on the test listing page.
+   */
+  Drupal.behaviors.simpleTestGroupCollapse = {
+    attach: function (context) {
+      $(context).find('.simpletest-group').once('simpletest-group-collapse').each(function () {
+        var $group = $(this);
+        var $image = $group.find('.simpletest-image');
+        $image
+          .html(drupalSettings.simpleTest.images[0])
+          .on('click', function () {
+            var $tests = $group.nextUntil('.simpletest-group');
+            var expand = !$group.hasClass('expanded');
+            $group.toggleClass('expanded', expand);
+            $tests.toggleClass('js-hide', !expand);
+            $image.html(drupalSettings.simpleTest.images[+expand]);
+          });
+      });
+    }
+  };
+
+  /**
+   * Toggles test checkboxes to match the group checkbox.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches behavior for selecting all tests in a group.
+   */
+  Drupal.behaviors.simpleTestSelectAll = {
+    attach: function (context) {
+      $(context).find('.simpletest-group').once('simpletest-group-select-all').each(function () {
+        var $group = $(this);
+        var $cell = $group.find('.simpletest-group-select-all');
+        var $groupCheckbox = $('<input type="checkbox" id="' + $cell.attr('id') + '-group-select-all" class="form-checkbox" />');
+        var $testCheckboxes = $group.nextUntil('.simpletest-group').find('input[type=checkbox]');
+        $cell.append($groupCheckbox);
+
+        // Toggle the test checkboxes when the group checkbox is toggled.
+        $groupCheckbox.on('change', function () {
+          var checked = $(this).prop('checked');
+          $testCheckboxes.prop('checked', checked);
+        });
+
+        // Update the group checkbox when a test checkbox is toggled.
+        function updateGroupCheckbox() {
+          var allChecked = true;
+          $testCheckboxes.each(function () {
+            if (!$(this).prop('checked')) {
+              allChecked = false;
+              return false;
+            }
+          });
+          $groupCheckbox.prop('checked', allChecked);
+        }
+
+        $testCheckboxes.on('change', updateGroupCheckbox);
+      });
+    }
+  };
+
+  /**
+   * Filters the test list table by a text input search string.
+   *
+   * Text search input: input.table-filter-text
+   * Target table:      input.table-filter-text[data-table]
+   * Source text:       .table-filter-text-source
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches the filter behavior to the text input element.
+   */
+  Drupal.behaviors.simpletestTableFilterByText = {
+    attach: function (context) {
+      var $input = $('input.table-filter-text').once('table-filter-text');
+      var $table = $($input.attr('data-table'));
+      var $rows;
+      var searched = false;
+
+      function filterTestList(e) {
+        var query = $(e.target).val().toLowerCase();
+
+        function showTestRow(index, row) {
+          var $row = $(row);
+          var $sources = $row.find('.table-filter-text-source');
+          var textMatch = $sources.text().toLowerCase().indexOf(query) !== -1;
+          $row.closest('tr').toggle(textMatch);
+        }
+
+        // Filter if the length of the query is at least 3 characters.
+        if (query.length >= 3) {
+          // Indicate that a search has been performed, and hide the
+          // "select all" checkbox.
+          searched = true;
+          $('#simpletest-form-table thead th.select-all input').hide();
+
+          $rows.each(showTestRow);
+        }
+        // Restore to the original state if any searching has occurred.
+        else if (searched) {
+          searched = false;
+          $('#simpletest-form-table thead th.select-all input').show();
+          // Restore all rows to their original display state.
+          $rows.css('display', '');
+        }
+      }
+
+      if ($table.length) {
+        $rows = $table.find('tbody tr');
+        $input.trigger('focus').on('keyup', Drupal.debounce(filterTestList, 200));
+      }
+    }
+  };
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/simpletest/simpletest.js b/core/modules/simpletest/simpletest.js
index ba54cbf74b35..4bdcb1164fcd 100644
--- a/core/modules/simpletest/simpletest.js
+++ b/core/modules/simpletest/simpletest.js
@@ -1,48 +1,33 @@
 /**
- * @file
- * Simpletest behaviors.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/simpletest/simpletest.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings) {
 
   'use strict';
 
-  /**
-   * Collapses table rows followed by group rows on the test listing page.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attach collapse behavior on the test listing page.
-   */
   Drupal.behaviors.simpleTestGroupCollapse = {
-    attach: function (context) {
+    attach: function attach(context) {
       $(context).find('.simpletest-group').once('simpletest-group-collapse').each(function () {
         var $group = $(this);
         var $image = $group.find('.simpletest-image');
-        $image
-          .html(drupalSettings.simpleTest.images[0])
-          .on('click', function () {
-            var $tests = $group.nextUntil('.simpletest-group');
-            var expand = !$group.hasClass('expanded');
-            $group.toggleClass('expanded', expand);
-            $tests.toggleClass('js-hide', !expand);
-            $image.html(drupalSettings.simpleTest.images[+expand]);
-          });
+        $image.html(drupalSettings.simpleTest.images[0]).on('click', function () {
+          var $tests = $group.nextUntil('.simpletest-group');
+          var expand = !$group.hasClass('expanded');
+          $group.toggleClass('expanded', expand);
+          $tests.toggleClass('js-hide', !expand);
+          $image.html(drupalSettings.simpleTest.images[+expand]);
+        });
       });
     }
   };
 
-  /**
-   * Toggles test checkboxes to match the group checkbox.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches behavior for selecting all tests in a group.
-   */
   Drupal.behaviors.simpleTestSelectAll = {
-    attach: function (context) {
+    attach: function attach(context) {
       $(context).find('.simpletest-group').once('simpletest-group-select-all').each(function () {
         var $group = $(this);
         var $cell = $group.find('.simpletest-group-select-all');
@@ -50,13 +35,11 @@
         var $testCheckboxes = $group.nextUntil('.simpletest-group').find('input[type=checkbox]');
         $cell.append($groupCheckbox);
 
-        // Toggle the test checkboxes when the group checkbox is toggled.
         $groupCheckbox.on('change', function () {
           var checked = $(this).prop('checked');
           $testCheckboxes.prop('checked', checked);
         });
 
-        // Update the group checkbox when a test checkbox is toggled.
         function updateGroupCheckbox() {
           var allChecked = true;
           $testCheckboxes.each(function () {
@@ -73,20 +56,8 @@
     }
   };
 
-  /**
-   * Filters the test list table by a text input search string.
-   *
-   * Text search input: input.table-filter-text
-   * Target table:      input.table-filter-text[data-table]
-   * Source text:       .table-filter-text-source
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches the filter behavior to the text input element.
-   */
   Drupal.behaviors.simpletestTableFilterByText = {
-    attach: function (context) {
+    attach: function attach(context) {
       var $input = $('input.table-filter-text').once('table-filter-text');
       var $table = $($input.attr('data-table'));
       var $rows;
@@ -102,22 +73,17 @@
           $row.closest('tr').toggle(textMatch);
         }
 
-        // Filter if the length of the query is at least 3 characters.
         if (query.length >= 3) {
-          // Indicate that a search has been performed, and hide the
-          // "select all" checkbox.
           searched = true;
           $('#simpletest-form-table thead th.select-all input').hide();
 
           $rows.each(showTestRow);
-        }
-        // Restore to the original state if any searching has occurred.
-        else if (searched) {
-          searched = false;
-          $('#simpletest-form-table thead th.select-all input').show();
-          // Restore all rows to their original display state.
-          $rows.css('display', '');
-        }
+        } else if (searched) {
+            searched = false;
+            $('#simpletest-form-table thead th.select-all input').show();
+
+            $rows.css('display', '');
+          }
       }
 
       if ($table.length) {
@@ -126,5 +92,4 @@
       }
     }
   };
-
-})(jQuery, Drupal, drupalSettings);
+})(jQuery, Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/modules/statistics/statistics.es6.js b/core/modules/statistics/statistics.es6.js
new file mode 100644
index 000000000000..572cd456ff97
--- /dev/null
+++ b/core/modules/statistics/statistics.es6.js
@@ -0,0 +1,18 @@
+/**
+ * @file
+ * Statistics functionality.
+ */
+
+(function ($, Drupal, drupalSettings) {
+
+  'use strict';
+
+  $(document).ready(function () {
+    $.ajax({
+      type: 'POST',
+      cache: false,
+      url: drupalSettings.statistics.url,
+      data: drupalSettings.statistics.data
+    });
+  });
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/statistics/statistics.js b/core/modules/statistics/statistics.js
index 572cd456ff97..c781dd8ffa0d 100644
--- a/core/modules/statistics/statistics.js
+++ b/core/modules/statistics/statistics.js
@@ -1,7 +1,10 @@
 /**
- * @file
- * Statistics functionality.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/statistics/statistics.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings) {
 
@@ -15,4 +18,4 @@
       data: drupalSettings.statistics.data
     });
   });
-})(jQuery, Drupal, drupalSettings);
+})(jQuery, Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/modules/system/js/system.date.es6.js b/core/modules/system/js/system.date.es6.js
new file mode 100644
index 000000000000..678614d98c59
--- /dev/null
+++ b/core/modules/system/js/system.date.es6.js
@@ -0,0 +1,57 @@
+/**
+ * @file
+ * Provides date format preview feature.
+ */
+
+(function ($, Drupal, drupalSettings) {
+
+  'use strict';
+
+  var dateFormats = drupalSettings.dateFormats;
+
+  /**
+   * Display the preview for date format entered.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attach behavior for previewing date formats on input elements.
+   */
+  Drupal.behaviors.dateFormat = {
+    attach: function (context) {
+      var $context = $(context);
+      var $source = $context.find('[data-drupal-date-formatter="source"]').once('dateFormat');
+      var $target = $context.find('[data-drupal-date-formatter="preview"]').once('dateFormat');
+      var $preview = $target.find('em');
+
+      // All elements have to exist.
+      if (!$source.length || !$target.length) {
+        return;
+      }
+
+      /**
+       * Event handler that replaces date characters with value.
+       *
+       * @param {jQuery.Event} e
+       *   The jQuery event triggered.
+       */
+      function dateFormatHandler(e) {
+        var baseValue = $(e.target).val() || '';
+        var dateString = baseValue.replace(/\\?(.?)/gi, function (key, value) {
+          return dateFormats[key] ? dateFormats[key] : value;
+        });
+
+        $preview.html(dateString);
+        $target.toggleClass('js-hide', !dateString.length);
+      }
+
+      /**
+       * On given event triggers the date character replacement.
+       */
+      $source.on('keyup.dateFormat change.dateFormat input.dateFormat', dateFormatHandler)
+        // Initialize preview.
+        .trigger('keyup');
+    }
+  };
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/system/js/system.date.js b/core/modules/system/js/system.date.js
index 678614d98c59..e61097698642 100644
--- a/core/modules/system/js/system.date.js
+++ b/core/modules/system/js/system.date.js
@@ -1,7 +1,10 @@
 /**
- * @file
- * Provides date format preview feature.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/system/js/system.date.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings) {
 
@@ -9,32 +12,17 @@
 
   var dateFormats = drupalSettings.dateFormats;
 
-  /**
-   * Display the preview for date format entered.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attach behavior for previewing date formats on input elements.
-   */
   Drupal.behaviors.dateFormat = {
-    attach: function (context) {
+    attach: function attach(context) {
       var $context = $(context);
       var $source = $context.find('[data-drupal-date-formatter="source"]').once('dateFormat');
       var $target = $context.find('[data-drupal-date-formatter="preview"]').once('dateFormat');
       var $preview = $target.find('em');
 
-      // All elements have to exist.
       if (!$source.length || !$target.length) {
         return;
       }
 
-      /**
-       * Event handler that replaces date characters with value.
-       *
-       * @param {jQuery.Event} e
-       *   The jQuery event triggered.
-       */
       function dateFormatHandler(e) {
         var baseValue = $(e.target).val() || '';
         var dateString = baseValue.replace(/\\?(.?)/gi, function (key, value) {
@@ -45,13 +33,7 @@
         $target.toggleClass('js-hide', !dateString.length);
       }
 
-      /**
-       * On given event triggers the date character replacement.
-       */
-      $source.on('keyup.dateFormat change.dateFormat input.dateFormat', dateFormatHandler)
-        // Initialize preview.
-        .trigger('keyup');
+      $source.on('keyup.dateFormat change.dateFormat input.dateFormat', dateFormatHandler).trigger('keyup');
     }
   };
-
-})(jQuery, Drupal, drupalSettings);
+})(jQuery, Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/modules/system/js/system.es6.js b/core/modules/system/js/system.es6.js
new file mode 100644
index 000000000000..82f0de66871b
--- /dev/null
+++ b/core/modules/system/js/system.es6.js
@@ -0,0 +1,81 @@
+/**
+ * @file
+ * System behaviors.
+ */
+
+(function ($, Drupal, drupalSettings) {
+
+  'use strict';
+
+  // Cache IDs in an array for ease of use.
+  var ids = [];
+
+  /**
+   * Attaches field copy behavior from input fields to other input fields.
+   *
+   * When a field is filled out, apply its value to other fields that will
+   * likely use the same value. In the installer this is used to populate the
+   * administrator email address with the same value as the site email address.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches the field copy behavior to an input field.
+   */
+  Drupal.behaviors.copyFieldValue = {
+    attach: function (context) {
+      // List of fields IDs on which to bind the event listener.
+      // Create an array of IDs to use with jQuery.
+      for (var sourceId in drupalSettings.copyFieldValue) {
+        if (drupalSettings.copyFieldValue.hasOwnProperty(sourceId)) {
+          ids.push(sourceId);
+        }
+      }
+      if (ids.length) {
+        // Listen to value:copy events on all dependent fields.
+        // We have to use body and not document because of the way jQuery events
+        // bubble up the DOM tree.
+        $('body').once('copy-field-values').on('value:copy', this.valueTargetCopyHandler);
+        // Listen on all source elements.
+        $('#' + ids.join(', #')).once('copy-field-values').on('blur', this.valueSourceBlurHandler);
+      }
+    },
+    detach: function (context, settings, trigger) {
+      if (trigger === 'unload' && ids.length) {
+        $('body').removeOnce('copy-field-values').off('value:copy');
+        $('#' + ids.join(', #')).removeOnce('copy-field-values').off('blur');
+      }
+    },
+
+    /**
+     * Event handler that fill the target element with the specified value.
+     *
+     * @param {jQuery.Event} e
+     *   Event object.
+     * @param {string} value
+     *   Custom value from jQuery trigger.
+     */
+    valueTargetCopyHandler: function (e, value) {
+      var $target = $(e.target);
+      if ($target.val() === '') {
+        $target.val(value);
+      }
+    },
+
+    /**
+     * Handler for a Blur event on a source field.
+     *
+     * This event handler will trigger a 'value:copy' event on all dependent
+     * fields.
+     *
+     * @param {jQuery.Event} e
+     *   The event triggered.
+     */
+    valueSourceBlurHandler: function (e) {
+      var value = $(e.target).val();
+      var targetIds = drupalSettings.copyFieldValue[e.target.id];
+      $('#' + targetIds.join(', #')).trigger('value:copy', value);
+    }
+  };
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/system/js/system.js b/core/modules/system/js/system.js
index 82f0de66871b..56c29f433d79 100644
--- a/core/modules/system/js/system.js
+++ b/core/modules/system/js/system.js
@@ -1,81 +1,48 @@
 /**
- * @file
- * System behaviors.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/system/js/system.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings) {
 
   'use strict';
 
-  // Cache IDs in an array for ease of use.
   var ids = [];
 
-  /**
-   * Attaches field copy behavior from input fields to other input fields.
-   *
-   * When a field is filled out, apply its value to other fields that will
-   * likely use the same value. In the installer this is used to populate the
-   * administrator email address with the same value as the site email address.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches the field copy behavior to an input field.
-   */
   Drupal.behaviors.copyFieldValue = {
-    attach: function (context) {
-      // List of fields IDs on which to bind the event listener.
-      // Create an array of IDs to use with jQuery.
+    attach: function attach(context) {
       for (var sourceId in drupalSettings.copyFieldValue) {
         if (drupalSettings.copyFieldValue.hasOwnProperty(sourceId)) {
           ids.push(sourceId);
         }
       }
       if (ids.length) {
-        // Listen to value:copy events on all dependent fields.
-        // We have to use body and not document because of the way jQuery events
-        // bubble up the DOM tree.
         $('body').once('copy-field-values').on('value:copy', this.valueTargetCopyHandler);
-        // Listen on all source elements.
+
         $('#' + ids.join(', #')).once('copy-field-values').on('blur', this.valueSourceBlurHandler);
       }
     },
-    detach: function (context, settings, trigger) {
+    detach: function detach(context, settings, trigger) {
       if (trigger === 'unload' && ids.length) {
         $('body').removeOnce('copy-field-values').off('value:copy');
         $('#' + ids.join(', #')).removeOnce('copy-field-values').off('blur');
       }
     },
 
-    /**
-     * Event handler that fill the target element with the specified value.
-     *
-     * @param {jQuery.Event} e
-     *   Event object.
-     * @param {string} value
-     *   Custom value from jQuery trigger.
-     */
-    valueTargetCopyHandler: function (e, value) {
+    valueTargetCopyHandler: function valueTargetCopyHandler(e, value) {
       var $target = $(e.target);
       if ($target.val() === '') {
         $target.val(value);
       }
     },
 
-    /**
-     * Handler for a Blur event on a source field.
-     *
-     * This event handler will trigger a 'value:copy' event on all dependent
-     * fields.
-     *
-     * @param {jQuery.Event} e
-     *   The event triggered.
-     */
-    valueSourceBlurHandler: function (e) {
+    valueSourceBlurHandler: function valueSourceBlurHandler(e) {
       var value = $(e.target).val();
       var targetIds = drupalSettings.copyFieldValue[e.target.id];
       $('#' + targetIds.join(', #')).trigger('value:copy', value);
     }
   };
-
-})(jQuery, Drupal, drupalSettings);
+})(jQuery, Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/modules/system/js/system.modules.es6.js b/core/modules/system/js/system.modules.es6.js
new file mode 100644
index 000000000000..d47ca7031e8f
--- /dev/null
+++ b/core/modules/system/js/system.modules.es6.js
@@ -0,0 +1,103 @@
+/**
+ * @file
+ * Module page behaviors.
+ */
+
+(function ($, Drupal, debounce) {
+
+  'use strict';
+
+  /**
+   * Filters the module list table by a text input search string.
+   *
+   * Additionally accounts for multiple tables being wrapped in "package" details
+   * elements.
+   *
+   * Text search input: input.table-filter-text
+   * Target table:      input.table-filter-text[data-table]
+   * Source text:       .table-filter-text-source, .module-name, .module-description
+   *
+   * @type {Drupal~behavior}
+   */
+  Drupal.behaviors.tableFilterByText = {
+    attach: function (context, settings) {
+      var $input = $('input.table-filter-text').once('table-filter-text');
+      var $table = $($input.attr('data-table'));
+      var $rowsAndDetails;
+      var $rows;
+      var $details;
+      var searching = false;
+
+      function hidePackageDetails(index, element) {
+        var $packDetails = $(element);
+        var $visibleRows = $packDetails.find('tbody tr:visible');
+        $packDetails.toggle($visibleRows.length > 0);
+      }
+
+      function filterModuleList(e) {
+        var query = $(e.target).val();
+        // Case insensitive expression to find query at the beginning of a word.
+        var re = new RegExp('\\b' + query, 'i');
+
+        function showModuleRow(index, row) {
+          var $row = $(row);
+          var $sources = $row.find('.table-filter-text-source, .module-name, .module-description');
+          var textMatch = $sources.text().search(re) !== -1;
+          $row.closest('tr').toggle(textMatch);
+        }
+        // Search over all rows and packages.
+        $rowsAndDetails.show();
+
+        // Filter if the length of the query is at least 2 characters.
+        if (query.length >= 2) {
+          searching = true;
+          $rows.each(showModuleRow);
+
+          // Note that we first open all <details> to be able to use ':visible'.
+          // Mark the <details> elements that were closed before filtering, so
+          // they can be reclosed when filtering is removed.
+          $details.not('[open]').attr('data-drupal-system-state', 'forced-open');
+
+          // Hide the package <details> if they don't have any visible rows.
+          // Note that we first show() all <details> to be able to use ':visible'.
+          $details.attr('open', true).each(hidePackageDetails);
+
+          Drupal.announce(
+            Drupal.t(
+              '!modules modules are available in the modified list.',
+              {'!modules': $rowsAndDetails.find('tbody tr:visible').length}
+            )
+          );
+        }
+        else if (searching) {
+          searching = false;
+          $rowsAndDetails.show();
+          // Return <details> elements that had been closed before filtering
+          // to a closed state.
+          $details.filter('[data-drupal-system-state="forced-open"]')
+            .removeAttr('data-drupal-system-state')
+            .attr('open', false);
+        }
+      }
+
+      function preventEnterKey(event) {
+        if (event.which === 13) {
+          event.preventDefault();
+          event.stopPropagation();
+        }
+      }
+
+      if ($table.length) {
+        $rowsAndDetails = $table.find('tr, details');
+        $rows = $table.find('tbody tr');
+        $details = $rowsAndDetails.filter('.package-listing');
+
+        $input.on({
+          keyup: debounce(filterModuleList, 200),
+          keydown: preventEnterKey
+        });
+      }
+    }
+  };
+
+}(jQuery, Drupal, Drupal.debounce));
diff --git a/core/modules/system/js/system.modules.js b/core/modules/system/js/system.modules.js
index d47ca7031e8f..23dbcbeecced 100644
--- a/core/modules/system/js/system.modules.js
+++ b/core/modules/system/js/system.modules.js
@@ -1,26 +1,17 @@
 /**
- * @file
- * Module page behaviors.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/system/js/system.modules.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, debounce) {
 
   'use strict';
 
-  /**
-   * Filters the module list table by a text input search string.
-   *
-   * Additionally accounts for multiple tables being wrapped in "package" details
-   * elements.
-   *
-   * Text search input: input.table-filter-text
-   * Target table:      input.table-filter-text[data-table]
-   * Source text:       .table-filter-text-source, .module-name, .module-description
-   *
-   * @type {Drupal~behavior}
-   */
   Drupal.behaviors.tableFilterByText = {
-    attach: function (context, settings) {
+    attach: function attach(context, settings) {
       var $input = $('input.table-filter-text').once('table-filter-text');
       var $table = $($input.attr('data-table'));
       var $rowsAndDetails;
@@ -36,7 +27,7 @@
 
       function filterModuleList(e) {
         var query = $(e.target).val();
-        // Case insensitive expression to find query at the beginning of a word.
+
         var re = new RegExp('\\b' + query, 'i');
 
         function showModuleRow(index, row) {
@@ -45,38 +36,23 @@
           var textMatch = $sources.text().search(re) !== -1;
           $row.closest('tr').toggle(textMatch);
         }
-        // Search over all rows and packages.
+
         $rowsAndDetails.show();
 
-        // Filter if the length of the query is at least 2 characters.
         if (query.length >= 2) {
           searching = true;
           $rows.each(showModuleRow);
 
-          // Note that we first open all <details> to be able to use ':visible'.
-          // Mark the <details> elements that were closed before filtering, so
-          // they can be reclosed when filtering is removed.
           $details.not('[open]').attr('data-drupal-system-state', 'forced-open');
 
-          // Hide the package <details> if they don't have any visible rows.
-          // Note that we first show() all <details> to be able to use ':visible'.
           $details.attr('open', true).each(hidePackageDetails);
 
-          Drupal.announce(
-            Drupal.t(
-              '!modules modules are available in the modified list.',
-              {'!modules': $rowsAndDetails.find('tbody tr:visible').length}
-            )
-          );
-        }
-        else if (searching) {
+          Drupal.announce(Drupal.t('!modules modules are available in the modified list.', { '!modules': $rowsAndDetails.find('tbody tr:visible').length }));
+        } else if (searching) {
           searching = false;
           $rowsAndDetails.show();
-          // Return <details> elements that had been closed before filtering
-          // to a closed state.
-          $details.filter('[data-drupal-system-state="forced-open"]')
-            .removeAttr('data-drupal-system-state')
-            .attr('open', false);
+
+          $details.filter('[data-drupal-system-state="forced-open"]').removeAttr('data-drupal-system-state').attr('open', false);
         }
       }
 
@@ -99,5 +75,4 @@
       }
     }
   };
-
-}(jQuery, Drupal, Drupal.debounce));
+})(jQuery, Drupal, Drupal.debounce);
\ No newline at end of file
diff --git a/core/modules/system/tests/modules/js_webassert_test/js/js_webassert_test.wait_for_ajax_request.es6.js b/core/modules/system/tests/modules/js_webassert_test/js/js_webassert_test.wait_for_ajax_request.es6.js
new file mode 100644
index 000000000000..3fa82d98bc9d
--- /dev/null
+++ b/core/modules/system/tests/modules/js_webassert_test/js/js_webassert_test.wait_for_ajax_request.es6.js
@@ -0,0 +1,22 @@
+/**
+ * @file
+ *  Testing behavior for JSWebAssertTest.
+ */
+
+(function ($, Drupal, drupalSettings) {
+
+  'use strict';
+
+  /**
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Makes changes in the DOM to be able to test the completion of AJAX in assertWaitOnAjaxRequest.
+   */
+  Drupal.behaviors.js_webassert_test_wait_for_ajax_request = {
+    attach: function (context) {
+      $('input[name="test_assert_wait_on_ajax_input"]').val('js_webassert_test');
+    }
+  };
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/system/tests/modules/js_webassert_test/js/js_webassert_test.wait_for_ajax_request.js b/core/modules/system/tests/modules/js_webassert_test/js/js_webassert_test.wait_for_ajax_request.js
index 3fa82d98bc9d..2a8ce93bbfc6 100644
--- a/core/modules/system/tests/modules/js_webassert_test/js/js_webassert_test.wait_for_ajax_request.js
+++ b/core/modules/system/tests/modules/js_webassert_test/js/js_webassert_test.wait_for_ajax_request.js
@@ -1,22 +1,18 @@
 /**
- * @file
- *  Testing behavior for JSWebAssertTest.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/system/tests/modules/js_webassert_test/js/js_webassert_test.wait_for_ajax_request.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings) {
 
   'use strict';
 
-  /**
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Makes changes in the DOM to be able to test the completion of AJAX in assertWaitOnAjaxRequest.
-   */
   Drupal.behaviors.js_webassert_test_wait_for_ajax_request = {
-    attach: function (context) {
+    attach: function attach(context) {
       $('input[name="test_assert_wait_on_ajax_input"]').val('js_webassert_test');
     }
   };
-
-})(jQuery, Drupal, drupalSettings);
+})(jQuery, Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/modules/system/tests/modules/js_webassert_test/js/js_webassert_test.wait_for_element.es6.js b/core/modules/system/tests/modules/js_webassert_test/js/js_webassert_test.wait_for_element.es6.js
new file mode 100644
index 000000000000..e11067b24cf8
--- /dev/null
+++ b/core/modules/system/tests/modules/js_webassert_test/js/js_webassert_test.wait_for_element.es6.js
@@ -0,0 +1,22 @@
+/**
+ * @file
+ *  Testing behavior for JSWebAssertTest.
+ */
+
+(function ($, Drupal, drupalSettings) {
+
+  'use strict';
+
+  /**
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Makes changes in the DOM to be able to test the completion of AJAX in assertWaitOnAjaxRequest.
+   */
+  Drupal.behaviors.js_webassert_test_wait_for_element = {
+    attach: function (context) {
+      $('#js_webassert_test_element_invisible').show();
+    }
+  };
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/system/tests/modules/js_webassert_test/js/js_webassert_test.wait_for_element.js b/core/modules/system/tests/modules/js_webassert_test/js/js_webassert_test.wait_for_element.js
index e11067b24cf8..61b7c787c01b 100644
--- a/core/modules/system/tests/modules/js_webassert_test/js/js_webassert_test.wait_for_element.js
+++ b/core/modules/system/tests/modules/js_webassert_test/js/js_webassert_test.wait_for_element.js
@@ -1,22 +1,18 @@
 /**
- * @file
- *  Testing behavior for JSWebAssertTest.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/system/tests/modules/js_webassert_test/js/js_webassert_test.wait_for_element.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings) {
 
   'use strict';
 
-  /**
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Makes changes in the DOM to be able to test the completion of AJAX in assertWaitOnAjaxRequest.
-   */
   Drupal.behaviors.js_webassert_test_wait_for_element = {
-    attach: function (context) {
+    attach: function attach(context) {
       $('#js_webassert_test_element_invisible').show();
     }
   };
-
-})(jQuery, Drupal, drupalSettings);
+})(jQuery, Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.es6.js b/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.es6.js
new file mode 100644
index 000000000000..39bf0bb5724f
--- /dev/null
+++ b/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.es6.js
@@ -0,0 +1,6 @@
+/**
+ * @file
+ * This file is for testing asset file inclusion, no contents are necessary.
+ *
+ * @ignore
+ */
diff --git a/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.js b/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.js
index 39bf0bb5724f..b106e35febfc 100644
--- a/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.js
+++ b/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.js
@@ -1,6 +1,7 @@
 /**
- * @file
- * This file is for testing asset file inclusion, no contents are necessary.
- *
- * @ignore
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/system/tests/modules/twig_theme_test/twig_theme_test.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
\ No newline at end of file
diff --git a/core/modules/system/tests/themes/test_theme/js/collapse.es6.js b/core/modules/system/tests/themes/test_theme/js/collapse.es6.js
new file mode 100644
index 000000000000..4d66841e8164
--- /dev/null
+++ b/core/modules/system/tests/themes/test_theme/js/collapse.es6.js
@@ -0,0 +1,4 @@
+/**
+ * @file
+ * Test JS asset file for test_theme.theme.
+ */
diff --git a/core/modules/system/tests/themes/test_theme/js/collapse.js b/core/modules/system/tests/themes/test_theme/js/collapse.js
index 4d66841e8164..794f4a5a5b90 100644
--- a/core/modules/system/tests/themes/test_theme/js/collapse.js
+++ b/core/modules/system/tests/themes/test_theme/js/collapse.js
@@ -1,4 +1,7 @@
 /**
- * @file
- * Test JS asset file for test_theme.theme.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/system/tests/themes/test_theme/js/collapse.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
\ No newline at end of file
diff --git a/core/modules/taxonomy/taxonomy.es6.js b/core/modules/taxonomy/taxonomy.es6.js
new file mode 100644
index 000000000000..99338d5a8ec2
--- /dev/null
+++ b/core/modules/taxonomy/taxonomy.es6.js
@@ -0,0 +1,56 @@
+/**
+ * @file
+ * Taxonomy behaviors.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * Move a block in the blocks table from one region to another.
+   *
+   * This behavior is dependent on the tableDrag behavior, since it uses the
+   * objects initialized in that behavior to update the row.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches the drag behavior to a applicable table element.
+   */
+  Drupal.behaviors.termDrag = {
+    attach: function (context, settings) {
+      var backStep = settings.taxonomy.backStep;
+      var forwardStep = settings.taxonomy.forwardStep;
+      // Get the blocks tableDrag object.
+      var tableDrag = Drupal.tableDrag.taxonomy;
+      var $table = $('#taxonomy');
+      var rows = $table.find('tr').length;
+
+      // When a row is swapped, keep previous and next page classes set.
+      tableDrag.row.prototype.onSwap = function (swappedRow) {
+        $table.find('tr.taxonomy-term-preview').removeClass('taxonomy-term-preview');
+        $table.find('tr.taxonomy-term-divider-top').removeClass('taxonomy-term-divider-top');
+        $table.find('tr.taxonomy-term-divider-bottom').removeClass('taxonomy-term-divider-bottom');
+
+        var tableBody = $table[0].tBodies[0];
+        if (backStep) {
+          for (var n = 0; n < backStep; n++) {
+            $(tableBody.rows[n]).addClass('taxonomy-term-preview');
+          }
+          $(tableBody.rows[backStep - 1]).addClass('taxonomy-term-divider-top');
+          $(tableBody.rows[backStep]).addClass('taxonomy-term-divider-bottom');
+        }
+
+        if (forwardStep) {
+          for (var k = rows - forwardStep - 1; k < rows - 1; k++) {
+            $(tableBody.rows[k]).addClass('taxonomy-term-preview');
+          }
+          $(tableBody.rows[rows - forwardStep - 2]).addClass('taxonomy-term-divider-top');
+          $(tableBody.rows[rows - forwardStep - 1]).addClass('taxonomy-term-divider-bottom');
+        }
+      };
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/core/modules/taxonomy/taxonomy.js b/core/modules/taxonomy/taxonomy.js
index 99338d5a8ec2..dfbdff318336 100644
--- a/core/modules/taxonomy/taxonomy.js
+++ b/core/modules/taxonomy/taxonomy.js
@@ -1,33 +1,24 @@
 /**
- * @file
- * Taxonomy behaviors.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/taxonomy/taxonomy.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   * Move a block in the blocks table from one region to another.
-   *
-   * This behavior is dependent on the tableDrag behavior, since it uses the
-   * objects initialized in that behavior to update the row.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches the drag behavior to a applicable table element.
-   */
   Drupal.behaviors.termDrag = {
-    attach: function (context, settings) {
+    attach: function attach(context, settings) {
       var backStep = settings.taxonomy.backStep;
       var forwardStep = settings.taxonomy.forwardStep;
-      // Get the blocks tableDrag object.
+
       var tableDrag = Drupal.tableDrag.taxonomy;
       var $table = $('#taxonomy');
       var rows = $table.find('tr').length;
 
-      // When a row is swapped, keep previous and next page classes set.
       tableDrag.row.prototype.onSwap = function (swappedRow) {
         $table.find('tr.taxonomy-term-preview').removeClass('taxonomy-term-preview');
         $table.find('tr.taxonomy-term-divider-top').removeClass('taxonomy-term-divider-top');
@@ -52,5 +43,4 @@
       };
     }
   };
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/modules/text/text.es6.js b/core/modules/text/text.es6.js
new file mode 100644
index 000000000000..ad89bd8ad161
--- /dev/null
+++ b/core/modules/text/text.es6.js
@@ -0,0 +1,61 @@
+/**
+ * @file
+ * Text behaviors.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * Auto-hide summary textarea if empty and show hide and unhide links.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches auto-hide behavior on `text-summary` events.
+   */
+  Drupal.behaviors.textSummary = {
+    attach: function (context, settings) {
+      $(context).find('.js-text-summary').once('text-summary').each(function () {
+        var $widget = $(this).closest('.js-text-format-wrapper');
+
+        var $summary = $widget.find('.js-text-summary-wrapper');
+        var $summaryLabel = $summary.find('label').eq(0);
+        var $full = $widget.find('.js-text-full').closest('.js-form-item');
+        var $fullLabel = $full.find('label').eq(0);
+
+        // Create a placeholder label when the field cardinality is greater
+        // than 1.
+        if ($fullLabel.length === 0) {
+          $fullLabel = $('<label></label>').prependTo($full);
+        }
+
+        // Set up the edit/hide summary link.
+        var $link = $('<span class="field-edit-link"> (<button type="button" class="link link-edit-summary">' + Drupal.t('Hide summary') + '</button>)</span>');
+        var $button = $link.find('button');
+        var toggleClick = true;
+        $link.on('click', function (e) {
+          if (toggleClick) {
+            $summary.hide();
+            $button.html(Drupal.t('Edit summary'));
+            $link.appendTo($fullLabel);
+          }
+          else {
+            $summary.show();
+            $button.html(Drupal.t('Hide summary'));
+            $link.appendTo($summaryLabel);
+          }
+          e.preventDefault();
+          toggleClick = !toggleClick;
+        }).appendTo($summaryLabel);
+
+        // If no summary is set, hide the summary field.
+        if ($widget.find('.js-text-summary').val() === '') {
+          $link.trigger('click');
+        }
+      });
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/core/modules/text/text.js b/core/modules/text/text.js
index ad89bd8ad161..556954c20184 100644
--- a/core/modules/text/text.js
+++ b/core/modules/text/text.js
@@ -1,22 +1,17 @@
 /**
- * @file
- * Text behaviors.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/text/text.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   * Auto-hide summary textarea if empty and show hide and unhide links.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches auto-hide behavior on `text-summary` events.
-   */
   Drupal.behaviors.textSummary = {
-    attach: function (context, settings) {
+    attach: function attach(context, settings) {
       $(context).find('.js-text-summary').once('text-summary').each(function () {
         var $widget = $(this).closest('.js-text-format-wrapper');
 
@@ -25,13 +20,10 @@
         var $full = $widget.find('.js-text-full').closest('.js-form-item');
         var $fullLabel = $full.find('label').eq(0);
 
-        // Create a placeholder label when the field cardinality is greater
-        // than 1.
         if ($fullLabel.length === 0) {
           $fullLabel = $('<label></label>').prependTo($full);
         }
 
-        // Set up the edit/hide summary link.
         var $link = $('<span class="field-edit-link"> (<button type="button" class="link link-edit-summary">' + Drupal.t('Hide summary') + '</button>)</span>');
         var $button = $link.find('button');
         var toggleClick = true;
@@ -40,8 +32,7 @@
             $summary.hide();
             $button.html(Drupal.t('Edit summary'));
             $link.appendTo($fullLabel);
-          }
-          else {
+          } else {
             $summary.show();
             $button.html(Drupal.t('Hide summary'));
             $link.appendTo($summaryLabel);
@@ -50,12 +41,10 @@
           toggleClick = !toggleClick;
         }).appendTo($summaryLabel);
 
-        // If no summary is set, hide the summary field.
         if ($widget.find('.js-text-summary').val() === '') {
           $link.trigger('click');
         }
       });
     }
   };
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/modules/toolbar/js/escapeAdmin.es6.js b/core/modules/toolbar/js/escapeAdmin.es6.js
new file mode 100644
index 000000000000..6e8f992fd41b
--- /dev/null
+++ b/core/modules/toolbar/js/escapeAdmin.es6.js
@@ -0,0 +1,48 @@
+/**
+ * @file
+ * Replaces the home link in toolbar with a back to site link.
+ */
+
+(function ($, Drupal, drupalSettings) {
+
+  'use strict';
+
+  var pathInfo = drupalSettings.path;
+  var escapeAdminPath = sessionStorage.getItem('escapeAdminPath');
+  var windowLocation = window.location;
+
+  // Saves the last non-administrative page in the browser to be able to link
+  // back to it when browsing administrative pages. If there is a destination
+  // parameter there is not need to save the current path because the page is
+  // loaded within an existing "workflow".
+  if (!pathInfo.currentPathIsAdmin && !/destination=/.test(windowLocation.search)) {
+    sessionStorage.setItem('escapeAdminPath', windowLocation);
+  }
+
+  /**
+   * Replaces the "Home" link with "Back to site" link.
+   *
+   * Back to site link points to the last non-administrative page the user
+   * visited within the same browser tab.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches the replacement functionality to the toolbar-escape-admin element.
+   */
+  Drupal.behaviors.escapeAdmin = {
+    attach: function () {
+      var $toolbarEscape = $('[data-toolbar-escape-admin]').once('escapeAdmin');
+      if ($toolbarEscape.length && pathInfo.currentPathIsAdmin) {
+        if (escapeAdminPath !== null) {
+          $toolbarEscape.attr('href', escapeAdminPath);
+        }
+        else {
+          $toolbarEscape.text(Drupal.t('Home'));
+        }
+        $toolbarEscape.closest('.toolbar-tab').removeClass('hidden');
+      }
+    }
+  };
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/toolbar/js/escapeAdmin.js b/core/modules/toolbar/js/escapeAdmin.js
index 6e8f992fd41b..757616cb8c5b 100644
--- a/core/modules/toolbar/js/escapeAdmin.js
+++ b/core/modules/toolbar/js/escapeAdmin.js
@@ -1,7 +1,10 @@
 /**
- * @file
- * Replaces the home link in toolbar with a back to site link.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/toolbar/js/escapeAdmin.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings) {
 
@@ -11,38 +14,21 @@
   var escapeAdminPath = sessionStorage.getItem('escapeAdminPath');
   var windowLocation = window.location;
 
-  // Saves the last non-administrative page in the browser to be able to link
-  // back to it when browsing administrative pages. If there is a destination
-  // parameter there is not need to save the current path because the page is
-  // loaded within an existing "workflow".
   if (!pathInfo.currentPathIsAdmin && !/destination=/.test(windowLocation.search)) {
     sessionStorage.setItem('escapeAdminPath', windowLocation);
   }
 
-  /**
-   * Replaces the "Home" link with "Back to site" link.
-   *
-   * Back to site link points to the last non-administrative page the user
-   * visited within the same browser tab.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches the replacement functionality to the toolbar-escape-admin element.
-   */
   Drupal.behaviors.escapeAdmin = {
-    attach: function () {
+    attach: function attach() {
       var $toolbarEscape = $('[data-toolbar-escape-admin]').once('escapeAdmin');
       if ($toolbarEscape.length && pathInfo.currentPathIsAdmin) {
         if (escapeAdminPath !== null) {
           $toolbarEscape.attr('href', escapeAdminPath);
-        }
-        else {
+        } else {
           $toolbarEscape.text(Drupal.t('Home'));
         }
         $toolbarEscape.closest('.toolbar-tab').removeClass('hidden');
       }
     }
   };
-
-})(jQuery, Drupal, drupalSettings);
+})(jQuery, Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/modules/toolbar/js/models/MenuModel.es6.js b/core/modules/toolbar/js/models/MenuModel.es6.js
new file mode 100644
index 000000000000..072a4f141513
--- /dev/null
+++ b/core/modules/toolbar/js/models/MenuModel.es6.js
@@ -0,0 +1,33 @@
+/**
+ * @file
+ * A Backbone Model for collapsible menus.
+ */
+
+(function (Backbone, Drupal) {
+
+  'use strict';
+
+  /**
+   * Backbone Model for collapsible menus.
+   *
+   * @constructor
+   *
+   * @augments Backbone.Model
+   */
+  Drupal.toolbar.MenuModel = Backbone.Model.extend(/** @lends Drupal.toolbar.MenuModel# */{
+
+    /**
+     * @type {object}
+     *
+     * @prop {object} subtrees
+     */
+    defaults: /** @lends Drupal.toolbar.MenuModel# */{
+
+      /**
+       * @type {object}
+       */
+      subtrees: {}
+    }
+  });
+
+}(Backbone, Drupal));
diff --git a/core/modules/toolbar/js/models/MenuModel.js b/core/modules/toolbar/js/models/MenuModel.js
index 072a4f141513..4e221a841f59 100644
--- a/core/modules/toolbar/js/models/MenuModel.js
+++ b/core/modules/toolbar/js/models/MenuModel.js
@@ -1,33 +1,18 @@
 /**
- * @file
- * A Backbone Model for collapsible menus.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/toolbar/js/models/MenuModel.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function (Backbone, Drupal) {
 
   'use strict';
 
-  /**
-   * Backbone Model for collapsible menus.
-   *
-   * @constructor
-   *
-   * @augments Backbone.Model
-   */
-  Drupal.toolbar.MenuModel = Backbone.Model.extend(/** @lends Drupal.toolbar.MenuModel# */{
-
-    /**
-     * @type {object}
-     *
-     * @prop {object} subtrees
-     */
-    defaults: /** @lends Drupal.toolbar.MenuModel# */{
-
-      /**
-       * @type {object}
-       */
+  Drupal.toolbar.MenuModel = Backbone.Model.extend({
+    defaults: {
       subtrees: {}
     }
   });
-
-}(Backbone, Drupal));
+})(Backbone, Drupal);
\ No newline at end of file
diff --git a/core/modules/toolbar/js/models/ToolbarModel.es6.js b/core/modules/toolbar/js/models/ToolbarModel.es6.js
new file mode 100644
index 000000000000..537601d8f712
--- /dev/null
+++ b/core/modules/toolbar/js/models/ToolbarModel.es6.js
@@ -0,0 +1,157 @@
+/**
+ * @file
+ * A Backbone Model for the toolbar.
+ */
+
+(function (Backbone, Drupal) {
+
+  'use strict';
+
+  /**
+   * Backbone model for the toolbar.
+   *
+   * @constructor
+   *
+   * @augments Backbone.Model
+   */
+  Drupal.toolbar.ToolbarModel = Backbone.Model.extend(/** @lends Drupal.toolbar.ToolbarModel# */{
+
+    /**
+     * @type {object}
+     *
+     * @prop activeTab
+     * @prop activeTray
+     * @prop isOriented
+     * @prop isFixed
+     * @prop areSubtreesLoaded
+     * @prop isViewportOverflowConstrained
+     * @prop orientation
+     * @prop locked
+     * @prop isTrayToggleVisible
+     * @prop height
+     * @prop offsets
+     */
+    defaults: /** @lends Drupal.toolbar.ToolbarModel# */{
+
+      /**
+       * The active toolbar tab. All other tabs should be inactive under
+       * normal circumstances. It will remain active across page loads. The
+       * active item is stored as an ID selector e.g. '#toolbar-item--1'.
+       *
+       * @type {string}
+       */
+      activeTab: null,
+
+      /**
+       * Represents whether a tray is open or not. Stored as an ID selector e.g.
+       * '#toolbar-item--1-tray'.
+       *
+       * @type {string}
+       */
+      activeTray: null,
+
+      /**
+       * Indicates whether the toolbar is displayed in an oriented fashion,
+       * either horizontal or vertical.
+       *
+       * @type {bool}
+       */
+      isOriented: false,
+
+      /**
+       * Indicates whether the toolbar is positioned absolute (false) or fixed
+       * (true).
+       *
+       * @type {bool}
+       */
+      isFixed: false,
+
+      /**
+       * Menu subtrees are loaded through an AJAX request only when the Toolbar
+       * is set to a vertical orientation.
+       *
+       * @type {bool}
+       */
+      areSubtreesLoaded: false,
+
+      /**
+       * If the viewport overflow becomes constrained, isFixed must be true so
+       * that elements in the trays aren't lost off-screen and impossible to
+       * get to.
+       *
+       * @type {bool}
+       */
+      isViewportOverflowConstrained: false,
+
+      /**
+       * The orientation of the active tray.
+       *
+       * @type {string}
+       */
+      orientation: 'vertical',
+
+      /**
+       * A tray is locked if a user toggled it to vertical. Otherwise a tray
+       * will switch between vertical and horizontal orientation based on the
+       * configured breakpoints. The locked state will be maintained across page
+       * loads.
+       *
+       * @type {bool}
+       */
+      locked: false,
+
+      /**
+       * Indicates whether the tray orientation toggle is visible.
+       *
+       * @type {bool}
+       */
+      isTrayToggleVisible: false,
+
+      /**
+       * The height of the toolbar.
+       *
+       * @type {number}
+       */
+      height: null,
+
+      /**
+       * The current viewport offsets determined by {@link Drupal.displace}. The
+       * offsets suggest how a module might position is components relative to
+       * the viewport.
+       *
+       * @type {object}
+       *
+       * @prop {number} top
+       * @prop {number} right
+       * @prop {number} bottom
+       * @prop {number} left
+       */
+      offsets: {
+        top: 0,
+        right: 0,
+        bottom: 0,
+        left: 0
+      }
+    },
+
+    /**
+     * @inheritdoc
+     *
+     * @param {object} attributes
+     *   Attributes for the toolbar.
+     * @param {object} options
+     *   Options for the toolbar.
+     *
+     * @return {string|undefined}
+     *   Returns an error message if validation failed.
+     */
+    validate: function (attributes, options) {
+      // Prevent the orientation being set to horizontal if it is locked, unless
+      // override has not been passed as an option.
+      if (attributes.orientation === 'horizontal' && this.get('locked') && !options.override) {
+        return Drupal.t('The toolbar cannot be set to a horizontal orientation when it is locked.');
+      }
+    }
+  });
+
+}(Backbone, Drupal));
diff --git a/core/modules/toolbar/js/models/ToolbarModel.js b/core/modules/toolbar/js/models/ToolbarModel.js
index 537601d8f712..45e254f7233f 100644
--- a/core/modules/toolbar/js/models/ToolbarModel.js
+++ b/core/modules/toolbar/js/models/ToolbarModel.js
@@ -1,131 +1,37 @@
 /**
- * @file
- * A Backbone Model for the toolbar.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/toolbar/js/models/ToolbarModel.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function (Backbone, Drupal) {
 
   'use strict';
 
-  /**
-   * Backbone model for the toolbar.
-   *
-   * @constructor
-   *
-   * @augments Backbone.Model
-   */
-  Drupal.toolbar.ToolbarModel = Backbone.Model.extend(/** @lends Drupal.toolbar.ToolbarModel# */{
-
-    /**
-     * @type {object}
-     *
-     * @prop activeTab
-     * @prop activeTray
-     * @prop isOriented
-     * @prop isFixed
-     * @prop areSubtreesLoaded
-     * @prop isViewportOverflowConstrained
-     * @prop orientation
-     * @prop locked
-     * @prop isTrayToggleVisible
-     * @prop height
-     * @prop offsets
-     */
-    defaults: /** @lends Drupal.toolbar.ToolbarModel# */{
-
-      /**
-       * The active toolbar tab. All other tabs should be inactive under
-       * normal circumstances. It will remain active across page loads. The
-       * active item is stored as an ID selector e.g. '#toolbar-item--1'.
-       *
-       * @type {string}
-       */
+  Drupal.toolbar.ToolbarModel = Backbone.Model.extend({
+    defaults: {
       activeTab: null,
 
-      /**
-       * Represents whether a tray is open or not. Stored as an ID selector e.g.
-       * '#toolbar-item--1-tray'.
-       *
-       * @type {string}
-       */
       activeTray: null,
 
-      /**
-       * Indicates whether the toolbar is displayed in an oriented fashion,
-       * either horizontal or vertical.
-       *
-       * @type {bool}
-       */
       isOriented: false,
 
-      /**
-       * Indicates whether the toolbar is positioned absolute (false) or fixed
-       * (true).
-       *
-       * @type {bool}
-       */
       isFixed: false,
 
-      /**
-       * Menu subtrees are loaded through an AJAX request only when the Toolbar
-       * is set to a vertical orientation.
-       *
-       * @type {bool}
-       */
       areSubtreesLoaded: false,
 
-      /**
-       * If the viewport overflow becomes constrained, isFixed must be true so
-       * that elements in the trays aren't lost off-screen and impossible to
-       * get to.
-       *
-       * @type {bool}
-       */
       isViewportOverflowConstrained: false,
 
-      /**
-       * The orientation of the active tray.
-       *
-       * @type {string}
-       */
       orientation: 'vertical',
 
-      /**
-       * A tray is locked if a user toggled it to vertical. Otherwise a tray
-       * will switch between vertical and horizontal orientation based on the
-       * configured breakpoints. The locked state will be maintained across page
-       * loads.
-       *
-       * @type {bool}
-       */
       locked: false,
 
-      /**
-       * Indicates whether the tray orientation toggle is visible.
-       *
-       * @type {bool}
-       */
       isTrayToggleVisible: false,
 
-      /**
-       * The height of the toolbar.
-       *
-       * @type {number}
-       */
       height: null,
 
-      /**
-       * The current viewport offsets determined by {@link Drupal.displace}. The
-       * offsets suggest how a module might position is components relative to
-       * the viewport.
-       *
-       * @type {object}
-       *
-       * @prop {number} top
-       * @prop {number} right
-       * @prop {number} bottom
-       * @prop {number} left
-       */
       offsets: {
         top: 0,
         right: 0,
@@ -134,24 +40,10 @@
       }
     },
 
-    /**
-     * @inheritdoc
-     *
-     * @param {object} attributes
-     *   Attributes for the toolbar.
-     * @param {object} options
-     *   Options for the toolbar.
-     *
-     * @return {string|undefined}
-     *   Returns an error message if validation failed.
-     */
-    validate: function (attributes, options) {
-      // Prevent the orientation being set to horizontal if it is locked, unless
-      // override has not been passed as an option.
+    validate: function validate(attributes, options) {
       if (attributes.orientation === 'horizontal' && this.get('locked') && !options.override) {
         return Drupal.t('The toolbar cannot be set to a horizontal orientation when it is locked.');
       }
     }
   });
-
-}(Backbone, Drupal));
+})(Backbone, Drupal);
\ No newline at end of file
diff --git a/core/modules/toolbar/js/toolbar.es6.js b/core/modules/toolbar/js/toolbar.es6.js
new file mode 100644
index 000000000000..8accad975420
--- /dev/null
+++ b/core/modules/toolbar/js/toolbar.es6.js
@@ -0,0 +1,257 @@
+/**
+ * @file
+ * Defines the behavior of the Drupal administration toolbar.
+ */
+
+(function ($, Drupal, drupalSettings) {
+
+  'use strict';
+
+  // Merge run-time settings with the defaults.
+  var options = $.extend(
+    {
+      breakpoints: {
+        'toolbar.narrow': '',
+        'toolbar.standard': '',
+        'toolbar.wide': ''
+      }
+    },
+    drupalSettings.toolbar,
+    // Merge strings on top of drupalSettings so that they are not mutable.
+    {
+      strings: {
+        horizontal: Drupal.t('Horizontal orientation'),
+        vertical: Drupal.t('Vertical orientation')
+      }
+    }
+  );
+
+  /**
+   * Registers tabs with the toolbar.
+   *
+   * The Drupal toolbar allows modules to register top-level tabs. These may
+   * point directly to a resource or toggle the visibility of a tray.
+   *
+   * Modules register tabs with hook_toolbar().
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches the toolbar rendering functionality to the toolbar element.
+   */
+  Drupal.behaviors.toolbar = {
+    attach: function (context) {
+      // Verify that the user agent understands media queries. Complex admin
+      // toolbar layouts require media query support.
+      if (!window.matchMedia('only screen').matches) {
+        return;
+      }
+      // Process the administrative toolbar.
+      $(context).find('#toolbar-administration').once('toolbar').each(function () {
+
+        // Establish the toolbar models and views.
+        var model = Drupal.toolbar.models.toolbarModel = new Drupal.toolbar.ToolbarModel({
+          locked: JSON.parse(localStorage.getItem('Drupal.toolbar.trayVerticalLocked')) || false,
+          activeTab: document.getElementById(JSON.parse(localStorage.getItem('Drupal.toolbar.activeTabID')))
+        });
+        Drupal.toolbar.views.toolbarVisualView = new Drupal.toolbar.ToolbarVisualView({
+          el: this,
+          model: model,
+          strings: options.strings
+        });
+        Drupal.toolbar.views.toolbarAuralView = new Drupal.toolbar.ToolbarAuralView({
+          el: this,
+          model: model,
+          strings: options.strings
+        });
+        Drupal.toolbar.views.bodyVisualView = new Drupal.toolbar.BodyVisualView({
+          el: this,
+          model: model
+        });
+
+        // Render collapsible menus.
+        var menuModel = Drupal.toolbar.models.menuModel = new Drupal.toolbar.MenuModel();
+        Drupal.toolbar.views.menuVisualView = new Drupal.toolbar.MenuVisualView({
+          el: $(this).find('.toolbar-menu-administration').get(0),
+          model: menuModel,
+          strings: options.strings
+        });
+
+        // Handle the resolution of Drupal.toolbar.setSubtrees.
+        // This is handled with a deferred so that the function may be invoked
+        // asynchronously.
+        Drupal.toolbar.setSubtrees.done(function (subtrees) {
+          menuModel.set('subtrees', subtrees);
+          var theme = drupalSettings.ajaxPageState.theme;
+          localStorage.setItem('Drupal.toolbar.subtrees.' + theme, JSON.stringify(subtrees));
+          // Indicate on the toolbarModel that subtrees are now loaded.
+          model.set('areSubtreesLoaded', true);
+        });
+
+        // Attach a listener to the configured media query breakpoints.
+        for (var label in options.breakpoints) {
+          if (options.breakpoints.hasOwnProperty(label)) {
+            var mq = options.breakpoints[label];
+            var mql = Drupal.toolbar.mql[label] = window.matchMedia(mq);
+            // Curry the model and the label of the media query breakpoint to
+            // the mediaQueryChangeHandler function.
+            mql.addListener(Drupal.toolbar.mediaQueryChangeHandler.bind(null, model, label));
+            // Fire the mediaQueryChangeHandler for each configured breakpoint
+            // so that they process once.
+            Drupal.toolbar.mediaQueryChangeHandler.call(null, model, label, mql);
+          }
+        }
+
+        // Trigger an initial attempt to load menu subitems. This first attempt
+        // is made after the media query handlers have had an opportunity to
+        // process. The toolbar starts in the vertical orientation by default,
+        // unless the viewport is wide enough to accommodate a horizontal
+        // orientation. Thus we give the Toolbar a chance to determine if it
+        // should be set to horizontal orientation before attempting to load
+        // menu subtrees.
+        Drupal.toolbar.views.toolbarVisualView.loadSubtrees();
+
+        $(document)
+          // Update the model when the viewport offset changes.
+          .on('drupalViewportOffsetChange.toolbar', function (event, offsets) {
+            model.set('offsets', offsets);
+          });
+
+        // Broadcast model changes to other modules.
+        model
+          .on('change:orientation', function (model, orientation) {
+            $(document).trigger('drupalToolbarOrientationChange', orientation);
+          })
+          .on('change:activeTab', function (model, tab) {
+            $(document).trigger('drupalToolbarTabChange', tab);
+          })
+          .on('change:activeTray', function (model, tray) {
+            $(document).trigger('drupalToolbarTrayChange', tray);
+          });
+
+        // If the toolbar's orientation is horizontal and no active tab is
+        // defined then show the tray of the first toolbar tab by default (but
+        // not the first 'Home' toolbar tab).
+        if (Drupal.toolbar.models.toolbarModel.get('orientation') === 'horizontal' && Drupal.toolbar.models.toolbarModel.get('activeTab') === null) {
+          Drupal.toolbar.models.toolbarModel.set({
+            activeTab: $('.toolbar-bar .toolbar-tab:not(.home-toolbar-tab) a').get(0)
+          });
+        }
+      });
+    }
+  };
+
+  /**
+   * Toolbar methods of Backbone objects.
+   *
+   * @namespace
+   */
+  Drupal.toolbar = {
+
+    /**
+     * A hash of View instances.
+     *
+     * @type {object.<string, Backbone.View>}
+     */
+    views: {},
+
+    /**
+     * A hash of Model instances.
+     *
+     * @type {object.<string, Backbone.Model>}
+     */
+    models: {},
+
+    /**
+     * A hash of MediaQueryList objects tracked by the toolbar.
+     *
+     * @type {object.<string, object>}
+     */
+    mql: {},
+
+    /**
+     * Accepts a list of subtree menu elements.
+     *
+     * A deferred object that is resolved by an inlined JavaScript callback.
+     *
+     * @type {jQuery.Deferred}
+     *
+     * @see toolbar_subtrees_jsonp().
+     */
+    setSubtrees: new $.Deferred(),
+
+    /**
+     * Respond to configured narrow media query changes.
+     *
+     * @param {Drupal.toolbar.ToolbarModel} model
+     *   A toolbar model
+     * @param {string} label
+     *   Media query label.
+     * @param {object} mql
+     *   A MediaQueryList object.
+     */
+    mediaQueryChangeHandler: function (model, label, mql) {
+      switch (label) {
+        case 'toolbar.narrow':
+          model.set({
+            isOriented: mql.matches,
+            isTrayToggleVisible: false
+          });
+          // If the toolbar doesn't have an explicit orientation yet, or if the
+          // narrow media query doesn't match then set the orientation to
+          // vertical.
+          if (!mql.matches || !model.get('orientation')) {
+            model.set({orientation: 'vertical'}, {validate: true});
+          }
+          break;
+
+        case 'toolbar.standard':
+          model.set({
+            isFixed: mql.matches
+          });
+          break;
+
+        case 'toolbar.wide':
+          model.set({
+            orientation: ((mql.matches) ? 'horizontal' : 'vertical')
+          }, {validate: true});
+          // The tray orientation toggle visibility does not need to be
+          // validated.
+          model.set({
+            isTrayToggleVisible: mql.matches
+          });
+          break;
+
+        default:
+          break;
+      }
+    }
+  };
+
+  /**
+   * A toggle is an interactive element often bound to a click handler.
+   *
+   * @return {string}
+   *   A string representing a DOM fragment.
+   */
+  Drupal.theme.toolbarOrientationToggle = function () {
+    return '<div class="toolbar-toggle-orientation"><div class="toolbar-lining">' +
+      '<button class="toolbar-icon" type="button"></button>' +
+      '</div></div>';
+  };
+
+  /**
+   * Ajax command to set the toolbar subtrees.
+   *
+   * @param {Drupal.Ajax} ajax
+   *   {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
+   * @param {object} response
+   *   JSON response from the Ajax request.
+   * @param {number} [status]
+   *   XMLHttpRequest status.
+   */
+  Drupal.AjaxCommands.prototype.setToolbarSubtrees = function (ajax, response, status) {
+    Drupal.toolbar.setSubtrees.resolve(response.subtrees);
+  };
+
+}(jQuery, Drupal, drupalSettings));
diff --git a/core/modules/toolbar/js/toolbar.js b/core/modules/toolbar/js/toolbar.js
index 8accad975420..a0819f4d434b 100644
--- a/core/modules/toolbar/js/toolbar.js
+++ b/core/modules/toolbar/js/toolbar.js
@@ -1,55 +1,35 @@
 /**
- * @file
- * Defines the behavior of the Drupal administration toolbar.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/toolbar/js/toolbar.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings) {
 
   'use strict';
 
-  // Merge run-time settings with the defaults.
-  var options = $.extend(
-    {
-      breakpoints: {
-        'toolbar.narrow': '',
-        'toolbar.standard': '',
-        'toolbar.wide': ''
-      }
-    },
-    drupalSettings.toolbar,
-    // Merge strings on top of drupalSettings so that they are not mutable.
-    {
-      strings: {
-        horizontal: Drupal.t('Horizontal orientation'),
-        vertical: Drupal.t('Vertical orientation')
-      }
+  var options = $.extend({
+    breakpoints: {
+      'toolbar.narrow': '',
+      'toolbar.standard': '',
+      'toolbar.wide': ''
     }
-  );
+  }, drupalSettings.toolbar, {
+    strings: {
+      horizontal: Drupal.t('Horizontal orientation'),
+      vertical: Drupal.t('Vertical orientation')
+    }
+  });
 
-  /**
-   * Registers tabs with the toolbar.
-   *
-   * The Drupal toolbar allows modules to register top-level tabs. These may
-   * point directly to a resource or toggle the visibility of a tray.
-   *
-   * Modules register tabs with hook_toolbar().
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches the toolbar rendering functionality to the toolbar element.
-   */
   Drupal.behaviors.toolbar = {
-    attach: function (context) {
-      // Verify that the user agent understands media queries. Complex admin
-      // toolbar layouts require media query support.
+    attach: function attach(context) {
       if (!window.matchMedia('only screen').matches) {
         return;
       }
-      // Process the administrative toolbar.
-      $(context).find('#toolbar-administration').once('toolbar').each(function () {
 
-        // Establish the toolbar models and views.
+      $(context).find('#toolbar-administration').once('toolbar').each(function () {
         var model = Drupal.toolbar.models.toolbarModel = new Drupal.toolbar.ToolbarModel({
           locked: JSON.parse(localStorage.getItem('Drupal.toolbar.trayVerticalLocked')) || false,
           activeTab: document.getElementById(JSON.parse(localStorage.getItem('Drupal.toolbar.activeTabID')))
@@ -69,7 +49,6 @@
           model: model
         });
 
-        // Render collapsible menus.
         var menuModel = Drupal.toolbar.models.menuModel = new Drupal.toolbar.MenuModel();
         Drupal.toolbar.views.menuVisualView = new Drupal.toolbar.MenuVisualView({
           el: $(this).find('.toolbar-menu-administration').get(0),
@@ -77,61 +56,39 @@
           strings: options.strings
         });
 
-        // Handle the resolution of Drupal.toolbar.setSubtrees.
-        // This is handled with a deferred so that the function may be invoked
-        // asynchronously.
         Drupal.toolbar.setSubtrees.done(function (subtrees) {
           menuModel.set('subtrees', subtrees);
           var theme = drupalSettings.ajaxPageState.theme;
           localStorage.setItem('Drupal.toolbar.subtrees.' + theme, JSON.stringify(subtrees));
-          // Indicate on the toolbarModel that subtrees are now loaded.
+
           model.set('areSubtreesLoaded', true);
         });
 
-        // Attach a listener to the configured media query breakpoints.
         for (var label in options.breakpoints) {
           if (options.breakpoints.hasOwnProperty(label)) {
             var mq = options.breakpoints[label];
             var mql = Drupal.toolbar.mql[label] = window.matchMedia(mq);
-            // Curry the model and the label of the media query breakpoint to
-            // the mediaQueryChangeHandler function.
+
             mql.addListener(Drupal.toolbar.mediaQueryChangeHandler.bind(null, model, label));
-            // Fire the mediaQueryChangeHandler for each configured breakpoint
-            // so that they process once.
+
             Drupal.toolbar.mediaQueryChangeHandler.call(null, model, label, mql);
           }
         }
 
-        // Trigger an initial attempt to load menu subitems. This first attempt
-        // is made after the media query handlers have had an opportunity to
-        // process. The toolbar starts in the vertical orientation by default,
-        // unless the viewport is wide enough to accommodate a horizontal
-        // orientation. Thus we give the Toolbar a chance to determine if it
-        // should be set to horizontal orientation before attempting to load
-        // menu subtrees.
         Drupal.toolbar.views.toolbarVisualView.loadSubtrees();
 
-        $(document)
-          // Update the model when the viewport offset changes.
-          .on('drupalViewportOffsetChange.toolbar', function (event, offsets) {
-            model.set('offsets', offsets);
-          });
+        $(document).on('drupalViewportOffsetChange.toolbar', function (event, offsets) {
+          model.set('offsets', offsets);
+        });
 
-        // Broadcast model changes to other modules.
-        model
-          .on('change:orientation', function (model, orientation) {
-            $(document).trigger('drupalToolbarOrientationChange', orientation);
-          })
-          .on('change:activeTab', function (model, tab) {
-            $(document).trigger('drupalToolbarTabChange', tab);
-          })
-          .on('change:activeTray', function (model, tray) {
-            $(document).trigger('drupalToolbarTrayChange', tray);
-          });
+        model.on('change:orientation', function (model, orientation) {
+          $(document).trigger('drupalToolbarOrientationChange', orientation);
+        }).on('change:activeTab', function (model, tab) {
+          $(document).trigger('drupalToolbarTabChange', tab);
+        }).on('change:activeTray', function (model, tray) {
+          $(document).trigger('drupalToolbarTrayChange', tray);
+        });
 
-        // If the toolbar's orientation is horizontal and no active tab is
-        // defined then show the tray of the first toolbar tab by default (but
-        // not the first 'Home' toolbar tab).
         if (Drupal.toolbar.models.toolbarModel.get('orientation') === 'horizontal' && Drupal.toolbar.models.toolbarModel.get('activeTab') === null) {
           Drupal.toolbar.models.toolbarModel.set({
             activeTab: $('.toolbar-bar .toolbar-tab:not(.home-toolbar-tab) a').get(0)
@@ -141,67 +98,25 @@
     }
   };
 
-  /**
-   * Toolbar methods of Backbone objects.
-   *
-   * @namespace
-   */
   Drupal.toolbar = {
-
-    /**
-     * A hash of View instances.
-     *
-     * @type {object.<string, Backbone.View>}
-     */
     views: {},
 
-    /**
-     * A hash of Model instances.
-     *
-     * @type {object.<string, Backbone.Model>}
-     */
     models: {},
 
-    /**
-     * A hash of MediaQueryList objects tracked by the toolbar.
-     *
-     * @type {object.<string, object>}
-     */
     mql: {},
 
-    /**
-     * Accepts a list of subtree menu elements.
-     *
-     * A deferred object that is resolved by an inlined JavaScript callback.
-     *
-     * @type {jQuery.Deferred}
-     *
-     * @see toolbar_subtrees_jsonp().
-     */
     setSubtrees: new $.Deferred(),
 
-    /**
-     * Respond to configured narrow media query changes.
-     *
-     * @param {Drupal.toolbar.ToolbarModel} model
-     *   A toolbar model
-     * @param {string} label
-     *   Media query label.
-     * @param {object} mql
-     *   A MediaQueryList object.
-     */
-    mediaQueryChangeHandler: function (model, label, mql) {
+    mediaQueryChangeHandler: function mediaQueryChangeHandler(model, label, mql) {
       switch (label) {
         case 'toolbar.narrow':
           model.set({
             isOriented: mql.matches,
             isTrayToggleVisible: false
           });
-          // If the toolbar doesn't have an explicit orientation yet, or if the
-          // narrow media query doesn't match then set the orientation to
-          // vertical.
+
           if (!mql.matches || !model.get('orientation')) {
-            model.set({orientation: 'vertical'}, {validate: true});
+            model.set({ orientation: 'vertical' }, { validate: true });
           }
           break;
 
@@ -213,10 +128,9 @@
 
         case 'toolbar.wide':
           model.set({
-            orientation: ((mql.matches) ? 'horizontal' : 'vertical')
-          }, {validate: true});
-          // The tray orientation toggle visibility does not need to be
-          // validated.
+            orientation: mql.matches ? 'horizontal' : 'vertical'
+          }, { validate: true });
+
           model.set({
             isTrayToggleVisible: mql.matches
           });
@@ -228,30 +142,11 @@
     }
   };
 
-  /**
-   * A toggle is an interactive element often bound to a click handler.
-   *
-   * @return {string}
-   *   A string representing a DOM fragment.
-   */
   Drupal.theme.toolbarOrientationToggle = function () {
-    return '<div class="toolbar-toggle-orientation"><div class="toolbar-lining">' +
-      '<button class="toolbar-icon" type="button"></button>' +
-      '</div></div>';
+    return '<div class="toolbar-toggle-orientation"><div class="toolbar-lining">' + '<button class="toolbar-icon" type="button"></button>' + '</div></div>';
   };
 
-  /**
-   * Ajax command to set the toolbar subtrees.
-   *
-   * @param {Drupal.Ajax} ajax
-   *   {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
-   * @param {object} response
-   *   JSON response from the Ajax request.
-   * @param {number} [status]
-   *   XMLHttpRequest status.
-   */
   Drupal.AjaxCommands.prototype.setToolbarSubtrees = function (ajax, response, status) {
     Drupal.toolbar.setSubtrees.resolve(response.subtrees);
   };
-
-}(jQuery, Drupal, drupalSettings));
+})(jQuery, Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/modules/toolbar/js/toolbar.menu.es6.js b/core/modules/toolbar/js/toolbar.menu.es6.js
new file mode 100644
index 000000000000..0ca25a9b0566
--- /dev/null
+++ b/core/modules/toolbar/js/toolbar.menu.es6.js
@@ -0,0 +1,197 @@
+/**
+ * @file
+ * Builds a nested accordion widget.
+ *
+ * Invoke on an HTML list element with the jQuery plugin pattern.
+ *
+ * @example
+ * $('.toolbar-menu').drupalToolbarMenu();
+ */
+
+(function ($, Drupal, drupalSettings) {
+
+  'use strict';
+
+  /**
+   * Store the open menu tray.
+   */
+  var activeItem = Drupal.url(drupalSettings.path.currentPath);
+
+  $.fn.drupalToolbarMenu = function () {
+
+    var ui = {
+      handleOpen: Drupal.t('Extend'),
+      handleClose: Drupal.t('Collapse')
+    };
+
+    /**
+     * Handle clicks from the disclosure button on an item with sub-items.
+     *
+     * @param {Object} event
+     *   A jQuery Event object.
+     */
+    function toggleClickHandler(event) {
+      var $toggle = $(event.target);
+      var $item = $toggle.closest('li');
+      // Toggle the list item.
+      toggleList($item);
+      // Close open sibling menus.
+      var $openItems = $item.siblings().filter('.open');
+      toggleList($openItems, false);
+    }
+
+    /**
+     * Handle clicks from a menu item link.
+     *
+     * @param {Object} event
+     *   A jQuery Event object.
+     */
+    function linkClickHandler(event) {
+      // If the toolbar is positioned fixed (and therefore hiding content
+      // underneath), then users expect clicks in the administration menu tray
+      // to take them to that destination but for the menu tray to be closed
+      // after clicking: otherwise the toolbar itself is obstructing the view
+      // of the destination they chose.
+      if (!Drupal.toolbar.models.toolbarModel.get('isFixed')) {
+        Drupal.toolbar.models.toolbarModel.set('activeTab', null);
+      }
+      // Stopping propagation to make sure that once a toolbar-box is clicked
+      // (the whitespace part), the page is not redirected anymore.
+      event.stopPropagation();
+    }
+
+    /**
+     * Toggle the open/close state of a list is a menu.
+     *
+     * @param {jQuery} $item
+     *   The li item to be toggled.
+     *
+     * @param {Boolean} switcher
+     *   A flag that forces toggleClass to add or a remove a class, rather than
+     *   simply toggling its presence.
+     */
+    function toggleList($item, switcher) {
+      var $toggle = $item.children('.toolbar-box').children('.toolbar-handle');
+      switcher = (typeof switcher !== 'undefined') ? switcher : !$item.hasClass('open');
+      // Toggle the item open state.
+      $item.toggleClass('open', switcher);
+      // Twist the toggle.
+      $toggle.toggleClass('open', switcher);
+      // Adjust the toggle text.
+      $toggle
+        .find('.action')
+        // Expand Structure, Collapse Structure.
+        .text((switcher) ? ui.handleClose : ui.handleOpen);
+    }
+
+    /**
+     * Add markup to the menu elements.
+     *
+     * Items with sub-elements have a list toggle attached to them. Menu item
+     * links and the corresponding list toggle are wrapped with in a div
+     * classed with .toolbar-box. The .toolbar-box div provides a positioning
+     * context for the item list toggle.
+     *
+     * @param {jQuery} $menu
+     *   The root of the menu to be initialized.
+     */
+    function initItems($menu) {
+      var options = {
+        class: 'toolbar-icon toolbar-handle',
+        action: ui.handleOpen,
+        text: ''
+      };
+      // Initialize items and their links.
+      $menu.find('li > a').wrap('<div class="toolbar-box">');
+      // Add a handle to each list item if it has a menu.
+      $menu.find('li').each(function (index, element) {
+        var $item = $(element);
+        if ($item.children('ul.toolbar-menu').length) {
+          var $box = $item.children('.toolbar-box');
+          options.text = Drupal.t('@label', {'@label': $box.find('a').text()});
+          $item.children('.toolbar-box')
+            .append(Drupal.theme('toolbarMenuItemToggle', options));
+        }
+      });
+    }
+
+    /**
+     * Adds a level class to each list based on its depth in the menu.
+     *
+     * This function is called recursively on each sub level of lists elements
+     * until the depth of the menu is exhausted.
+     *
+     * @param {jQuery} $lists
+     *   A jQuery object of ul elements.
+     *
+     * @param {number} level
+     *   The current level number to be assigned to the list elements.
+     */
+    function markListLevels($lists, level) {
+      level = (!level) ? 1 : level;
+      var $lis = $lists.children('li').addClass('level-' + level);
+      $lists = $lis.children('ul');
+      if ($lists.length) {
+        markListLevels($lists, level + 1);
+      }
+    }
+
+    /**
+     * On page load, open the active menu item.
+     *
+     * Marks the trail of the active link in the menu back to the root of the
+     * menu with .menu-item--active-trail.
+     *
+     * @param {jQuery} $menu
+     *   The root of the menu.
+     */
+    function openActiveItem($menu) {
+      var pathItem = $menu.find('a[href="' + location.pathname + '"]');
+      if (pathItem.length && !activeItem) {
+        activeItem = location.pathname;
+      }
+      if (activeItem) {
+        var $activeItem = $menu.find('a[href="' + activeItem + '"]').addClass('menu-item--active');
+        var $activeTrail = $activeItem.parentsUntil('.root', 'li').addClass('menu-item--active-trail');
+        toggleList($activeTrail, true);
+      }
+    }
+
+    // Return the jQuery object.
+    return this.each(function (selector) {
+      var $menu = $(this).once('toolbar-menu');
+      if ($menu.length) {
+        // Bind event handlers.
+        $menu
+          .on('click.toolbar', '.toolbar-box', toggleClickHandler)
+          .on('click.toolbar', '.toolbar-box a', linkClickHandler);
+
+        $menu.addClass('root');
+        initItems($menu);
+        markListLevels($menu);
+        // Restore previous and active states.
+        openActiveItem($menu);
+      }
+    });
+  };
+
+  /**
+   * A toggle is an interactive element often bound to a click handler.
+   *
+   * @param {object} options
+   *   Options for the button.
+   * @param {string} options.class
+   *   Class to set on the button.
+   * @param {string} options.action
+   *   Action for the button.
+   * @param {string} options.text
+   *   Used as label for the button.
+   *
+   * @return {string}
+   *   A string representing a DOM fragment.
+   */
+  Drupal.theme.toolbarMenuItemToggle = function (options) {
+    return '<button class="' + options['class'] + '"><span class="action">' + options.action + '</span><span class="label">' + options.text + '</span></button>';
+  };
+
+}(jQuery, Drupal, drupalSettings));
diff --git a/core/modules/toolbar/js/toolbar.menu.js b/core/modules/toolbar/js/toolbar.menu.js
index 0ca25a9b0566..97b142566183 100644
--- a/core/modules/toolbar/js/toolbar.menu.js
+++ b/core/modules/toolbar/js/toolbar.menu.js
@@ -1,20 +1,15 @@
 /**
- * @file
- * Builds a nested accordion widget.
- *
- * Invoke on an HTML list element with the jQuery plugin pattern.
- *
- * @example
- * $('.toolbar-menu').drupalToolbarMenu();
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/toolbar/js/toolbar.menu.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings) {
 
   'use strict';
 
-  /**
-   * Store the open menu tray.
-   */
   var activeItem = Drupal.url(drupalSettings.path.currentPath);
 
   $.fn.drupalToolbarMenu = function () {
@@ -24,111 +19,56 @@
       handleClose: Drupal.t('Collapse')
     };
 
-    /**
-     * Handle clicks from the disclosure button on an item with sub-items.
-     *
-     * @param {Object} event
-     *   A jQuery Event object.
-     */
     function toggleClickHandler(event) {
       var $toggle = $(event.target);
       var $item = $toggle.closest('li');
-      // Toggle the list item.
+
       toggleList($item);
-      // Close open sibling menus.
+
       var $openItems = $item.siblings().filter('.open');
       toggleList($openItems, false);
     }
 
-    /**
-     * Handle clicks from a menu item link.
-     *
-     * @param {Object} event
-     *   A jQuery Event object.
-     */
     function linkClickHandler(event) {
-      // If the toolbar is positioned fixed (and therefore hiding content
-      // underneath), then users expect clicks in the administration menu tray
-      // to take them to that destination but for the menu tray to be closed
-      // after clicking: otherwise the toolbar itself is obstructing the view
-      // of the destination they chose.
       if (!Drupal.toolbar.models.toolbarModel.get('isFixed')) {
         Drupal.toolbar.models.toolbarModel.set('activeTab', null);
       }
-      // Stopping propagation to make sure that once a toolbar-box is clicked
-      // (the whitespace part), the page is not redirected anymore.
+
       event.stopPropagation();
     }
 
-    /**
-     * Toggle the open/close state of a list is a menu.
-     *
-     * @param {jQuery} $item
-     *   The li item to be toggled.
-     *
-     * @param {Boolean} switcher
-     *   A flag that forces toggleClass to add or a remove a class, rather than
-     *   simply toggling its presence.
-     */
     function toggleList($item, switcher) {
       var $toggle = $item.children('.toolbar-box').children('.toolbar-handle');
-      switcher = (typeof switcher !== 'undefined') ? switcher : !$item.hasClass('open');
-      // Toggle the item open state.
+      switcher = typeof switcher !== 'undefined' ? switcher : !$item.hasClass('open');
+
       $item.toggleClass('open', switcher);
-      // Twist the toggle.
+
       $toggle.toggleClass('open', switcher);
-      // Adjust the toggle text.
-      $toggle
-        .find('.action')
-        // Expand Structure, Collapse Structure.
-        .text((switcher) ? ui.handleClose : ui.handleOpen);
+
+      $toggle.find('.action').text(switcher ? ui.handleClose : ui.handleOpen);
     }
 
-    /**
-     * Add markup to the menu elements.
-     *
-     * Items with sub-elements have a list toggle attached to them. Menu item
-     * links and the corresponding list toggle are wrapped with in a div
-     * classed with .toolbar-box. The .toolbar-box div provides a positioning
-     * context for the item list toggle.
-     *
-     * @param {jQuery} $menu
-     *   The root of the menu to be initialized.
-     */
     function initItems($menu) {
       var options = {
         class: 'toolbar-icon toolbar-handle',
         action: ui.handleOpen,
         text: ''
       };
-      // Initialize items and their links.
+
       $menu.find('li > a').wrap('<div class="toolbar-box">');
-      // Add a handle to each list item if it has a menu.
+
       $menu.find('li').each(function (index, element) {
         var $item = $(element);
         if ($item.children('ul.toolbar-menu').length) {
           var $box = $item.children('.toolbar-box');
-          options.text = Drupal.t('@label', {'@label': $box.find('a').text()});
-          $item.children('.toolbar-box')
-            .append(Drupal.theme('toolbarMenuItemToggle', options));
+          options.text = Drupal.t('@label', { '@label': $box.find('a').text() });
+          $item.children('.toolbar-box').append(Drupal.theme('toolbarMenuItemToggle', options));
         }
       });
     }
 
-    /**
-     * Adds a level class to each list based on its depth in the menu.
-     *
-     * This function is called recursively on each sub level of lists elements
-     * until the depth of the menu is exhausted.
-     *
-     * @param {jQuery} $lists
-     *   A jQuery object of ul elements.
-     *
-     * @param {number} level
-     *   The current level number to be assigned to the list elements.
-     */
     function markListLevels($lists, level) {
-      level = (!level) ? 1 : level;
+      level = !level ? 1 : level;
       var $lis = $lists.children('li').addClass('level-' + level);
       $lists = $lis.children('ul');
       if ($lists.length) {
@@ -136,15 +76,6 @@
       }
     }
 
-    /**
-     * On page load, open the active menu item.
-     *
-     * Marks the trail of the active link in the menu back to the root of the
-     * menu with .menu-item--active-trail.
-     *
-     * @param {jQuery} $menu
-     *   The root of the menu.
-     */
     function openActiveItem($menu) {
       var pathItem = $menu.find('a[href="' + location.pathname + '"]');
       if (pathItem.length && !activeItem) {
@@ -157,41 +88,21 @@
       }
     }
 
-    // Return the jQuery object.
     return this.each(function (selector) {
       var $menu = $(this).once('toolbar-menu');
       if ($menu.length) {
-        // Bind event handlers.
-        $menu
-          .on('click.toolbar', '.toolbar-box', toggleClickHandler)
-          .on('click.toolbar', '.toolbar-box a', linkClickHandler);
+        $menu.on('click.toolbar', '.toolbar-box', toggleClickHandler).on('click.toolbar', '.toolbar-box a', linkClickHandler);
 
         $menu.addClass('root');
         initItems($menu);
         markListLevels($menu);
-        // Restore previous and active states.
+
         openActiveItem($menu);
       }
     });
   };
 
-  /**
-   * A toggle is an interactive element often bound to a click handler.
-   *
-   * @param {object} options
-   *   Options for the button.
-   * @param {string} options.class
-   *   Class to set on the button.
-   * @param {string} options.action
-   *   Action for the button.
-   * @param {string} options.text
-   *   Used as label for the button.
-   *
-   * @return {string}
-   *   A string representing a DOM fragment.
-   */
   Drupal.theme.toolbarMenuItemToggle = function (options) {
     return '<button class="' + options['class'] + '"><span class="action">' + options.action + '</span><span class="label">' + options.text + '</span></button>';
   };
-
-}(jQuery, Drupal, drupalSettings));
+})(jQuery, Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/modules/toolbar/js/views/BodyVisualView.es6.js b/core/modules/toolbar/js/views/BodyVisualView.es6.js
new file mode 100644
index 000000000000..64593c9cf914
--- /dev/null
+++ b/core/modules/toolbar/js/views/BodyVisualView.es6.js
@@ -0,0 +1,53 @@
+/**
+ * @file
+ * A Backbone view for the body element.
+ */
+
+(function ($, Drupal, Backbone) {
+
+  'use strict';
+
+  Drupal.toolbar.BodyVisualView = Backbone.View.extend(/** @lends Drupal.toolbar.BodyVisualView# */{
+
+    /**
+     * Adjusts the body element with the toolbar position and dimension changes.
+     *
+     * @constructs
+     *
+     * @augments Backbone.View
+     */
+    initialize: function () {
+      this.listenTo(this.model, 'change:orientation change:offsets change:activeTray change:isOriented change:isFixed change:isViewportOverflowConstrained', this.render);
+    },
+
+    /**
+     * @inheritdoc
+     */
+    render: function () {
+      var $body = $('body');
+      var orientation = this.model.get('orientation');
+      var isOriented = this.model.get('isOriented');
+      var isViewportOverflowConstrained = this.model.get('isViewportOverflowConstrained');
+
+      $body
+        // We are using JavaScript to control media-query handling for two
+        // reasons: (1) Using JavaScript let's us leverage the breakpoint
+        // configurations and (2) the CSS is really complex if we try to hide
+        // some styling from browsers that don't understand CSS media queries.
+        // If we drive the CSS from classes added through JavaScript,
+        // then the CSS becomes simpler and more robust.
+        .toggleClass('toolbar-vertical', (orientation === 'vertical'))
+        .toggleClass('toolbar-horizontal', (isOriented && orientation === 'horizontal'))
+        // When the toolbar is fixed, it will not scroll with page scrolling.
+        .toggleClass('toolbar-fixed', (isViewportOverflowConstrained || this.model.get('isFixed')))
+        // Toggle the toolbar-tray-open class on the body element. The class is
+        // applied when a toolbar tray is active. Padding might be applied to
+        // the body element to prevent the tray from overlapping content.
+        .toggleClass('toolbar-tray-open', !!this.model.get('activeTray'))
+        // Apply padding to the top of the body to offset the placement of the
+        // toolbar bar element.
+        .css('padding-top', this.model.get('offsets').top);
+    }
+  });
+
+}(jQuery, Drupal, Backbone));
diff --git a/core/modules/toolbar/js/views/BodyVisualView.js b/core/modules/toolbar/js/views/BodyVisualView.js
index 64593c9cf914..e4aa12391d55 100644
--- a/core/modules/toolbar/js/views/BodyVisualView.js
+++ b/core/modules/toolbar/js/views/BodyVisualView.js
@@ -1,53 +1,27 @@
 /**
- * @file
- * A Backbone view for the body element.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/toolbar/js/views/BodyVisualView.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, Backbone) {
 
   'use strict';
 
-  Drupal.toolbar.BodyVisualView = Backbone.View.extend(/** @lends Drupal.toolbar.BodyVisualView# */{
-
-    /**
-     * Adjusts the body element with the toolbar position and dimension changes.
-     *
-     * @constructs
-     *
-     * @augments Backbone.View
-     */
-    initialize: function () {
+  Drupal.toolbar.BodyVisualView = Backbone.View.extend({
+    initialize: function initialize() {
       this.listenTo(this.model, 'change:orientation change:offsets change:activeTray change:isOriented change:isFixed change:isViewportOverflowConstrained', this.render);
     },
 
-    /**
-     * @inheritdoc
-     */
-    render: function () {
+    render: function render() {
       var $body = $('body');
       var orientation = this.model.get('orientation');
       var isOriented = this.model.get('isOriented');
       var isViewportOverflowConstrained = this.model.get('isViewportOverflowConstrained');
 
-      $body
-        // We are using JavaScript to control media-query handling for two
-        // reasons: (1) Using JavaScript let's us leverage the breakpoint
-        // configurations and (2) the CSS is really complex if we try to hide
-        // some styling from browsers that don't understand CSS media queries.
-        // If we drive the CSS from classes added through JavaScript,
-        // then the CSS becomes simpler and more robust.
-        .toggleClass('toolbar-vertical', (orientation === 'vertical'))
-        .toggleClass('toolbar-horizontal', (isOriented && orientation === 'horizontal'))
-        // When the toolbar is fixed, it will not scroll with page scrolling.
-        .toggleClass('toolbar-fixed', (isViewportOverflowConstrained || this.model.get('isFixed')))
-        // Toggle the toolbar-tray-open class on the body element. The class is
-        // applied when a toolbar tray is active. Padding might be applied to
-        // the body element to prevent the tray from overlapping content.
-        .toggleClass('toolbar-tray-open', !!this.model.get('activeTray'))
-        // Apply padding to the top of the body to offset the placement of the
-        // toolbar bar element.
-        .css('padding-top', this.model.get('offsets').top);
+      $body.toggleClass('toolbar-vertical', orientation === 'vertical').toggleClass('toolbar-horizontal', isOriented && orientation === 'horizontal').toggleClass('toolbar-fixed', isViewportOverflowConstrained || this.model.get('isFixed')).toggleClass('toolbar-tray-open', !!this.model.get('activeTray')).css('padding-top', this.model.get('offsets').top);
     }
   });
-
-}(jQuery, Drupal, Backbone));
+})(jQuery, Drupal, Backbone);
\ No newline at end of file
diff --git a/core/modules/toolbar/js/views/MenuVisualView.es6.js b/core/modules/toolbar/js/views/MenuVisualView.es6.js
new file mode 100644
index 000000000000..92dea215bf2c
--- /dev/null
+++ b/core/modules/toolbar/js/views/MenuVisualView.es6.js
@@ -0,0 +1,46 @@
+/**
+ * @file
+ * A Backbone view for the collapsible menus.
+ */
+
+(function ($, Backbone, Drupal) {
+
+  'use strict';
+
+  Drupal.toolbar.MenuVisualView = Backbone.View.extend(/** @lends Drupal.toolbar.MenuVisualView# */{
+
+    /**
+     * Backbone View for collapsible menus.
+     *
+     * @constructs
+     *
+     * @augments Backbone.View
+     */
+    initialize: function () {
+      this.listenTo(this.model, 'change:subtrees', this.render);
+    },
+
+    /**
+     * @inheritdoc
+     */
+    render: function () {
+      var subtrees = this.model.get('subtrees');
+      // Add subtrees.
+      for (var id in subtrees) {
+        if (subtrees.hasOwnProperty(id)) {
+          this.$el
+            .find('#toolbar-link-' + id)
+            .once('toolbar-subtrees')
+            .after(subtrees[id]);
+        }
+      }
+      // Render the main menu as a nested, collapsible accordion.
+      if ('drupalToolbarMenu' in $.fn) {
+        this.$el
+          .children('.toolbar-menu')
+          .drupalToolbarMenu();
+      }
+    }
+  });
+
+}(jQuery, Backbone, Drupal));
diff --git a/core/modules/toolbar/js/views/MenuVisualView.js b/core/modules/toolbar/js/views/MenuVisualView.js
index 92dea215bf2c..dd9f2877a5b4 100644
--- a/core/modules/toolbar/js/views/MenuVisualView.js
+++ b/core/modules/toolbar/js/views/MenuVisualView.js
@@ -1,46 +1,32 @@
 /**
- * @file
- * A Backbone view for the collapsible menus.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/toolbar/js/views/MenuVisualView.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Backbone, Drupal) {
 
   'use strict';
 
-  Drupal.toolbar.MenuVisualView = Backbone.View.extend(/** @lends Drupal.toolbar.MenuVisualView# */{
-
-    /**
-     * Backbone View for collapsible menus.
-     *
-     * @constructs
-     *
-     * @augments Backbone.View
-     */
-    initialize: function () {
+  Drupal.toolbar.MenuVisualView = Backbone.View.extend({
+    initialize: function initialize() {
       this.listenTo(this.model, 'change:subtrees', this.render);
     },
 
-    /**
-     * @inheritdoc
-     */
-    render: function () {
+    render: function render() {
       var subtrees = this.model.get('subtrees');
-      // Add subtrees.
+
       for (var id in subtrees) {
         if (subtrees.hasOwnProperty(id)) {
-          this.$el
-            .find('#toolbar-link-' + id)
-            .once('toolbar-subtrees')
-            .after(subtrees[id]);
+          this.$el.find('#toolbar-link-' + id).once('toolbar-subtrees').after(subtrees[id]);
         }
       }
-      // Render the main menu as a nested, collapsible accordion.
+
       if ('drupalToolbarMenu' in $.fn) {
-        this.$el
-          .children('.toolbar-menu')
-          .drupalToolbarMenu();
+        this.$el.children('.toolbar-menu').drupalToolbarMenu();
       }
     }
   });
-
-}(jQuery, Backbone, Drupal));
+})(jQuery, Backbone, Drupal);
\ No newline at end of file
diff --git a/core/modules/toolbar/js/views/ToolbarAuralView.es6.js b/core/modules/toolbar/js/views/ToolbarAuralView.es6.js
new file mode 100644
index 000000000000..3cc9adaf4c96
--- /dev/null
+++ b/core/modules/toolbar/js/views/ToolbarAuralView.es6.js
@@ -0,0 +1,70 @@
+/**
+ * @file
+ * A Backbone view for the aural feedback of the toolbar.
+ */
+
+(function (Backbone, Drupal) {
+
+  'use strict';
+
+  Drupal.toolbar.ToolbarAuralView = Backbone.View.extend(/** @lends Drupal.toolbar.ToolbarAuralView# */{
+
+    /**
+     * Backbone view for the aural feedback of the toolbar.
+     *
+     * @constructs
+     *
+     * @augments Backbone.View
+     *
+     * @param {object} options
+     *   Options for the view.
+     * @param {object} options.strings
+     *   Various strings to use in the view.
+     */
+    initialize: function (options) {
+      this.strings = options.strings;
+
+      this.listenTo(this.model, 'change:orientation', this.onOrientationChange);
+      this.listenTo(this.model, 'change:activeTray', this.onActiveTrayChange);
+    },
+
+    /**
+     * Announces an orientation change.
+     *
+     * @param {Drupal.toolbar.ToolbarModel} model
+     *   The toolbar model in question.
+     * @param {string} orientation
+     *   The new value of the orientation attribute in the model.
+     */
+    onOrientationChange: function (model, orientation) {
+      Drupal.announce(Drupal.t('Tray orientation changed to @orientation.', {
+        '@orientation': orientation
+      }));
+    },
+
+    /**
+     * Announces a changed active tray.
+     *
+     * @param {Drupal.toolbar.ToolbarModel} model
+     *   The toolbar model in question.
+     * @param {HTMLElement} tray
+     *   The new value of the tray attribute in the model.
+     */
+    onActiveTrayChange: function (model, tray) {
+      var relevantTray = (tray === null) ? model.previous('activeTray') : tray;
+      var action = (tray === null) ? Drupal.t('closed') : Drupal.t('opened');
+      var trayNameElement = relevantTray.querySelector('.toolbar-tray-name');
+      var text;
+      if (trayNameElement !== null) {
+        text = Drupal.t('Tray "@tray" @action.', {
+          '@tray': trayNameElement.textContent, '@action': action
+        });
+      }
+      else {
+        text = Drupal.t('Tray @action.', {'@action': action});
+      }
+      Drupal.announce(text);
+    }
+  });
+
+}(Backbone, Drupal));
diff --git a/core/modules/toolbar/js/views/ToolbarAuralView.js b/core/modules/toolbar/js/views/ToolbarAuralView.js
index 3cc9adaf4c96..59c2434d9c62 100644
--- a/core/modules/toolbar/js/views/ToolbarAuralView.js
+++ b/core/modules/toolbar/js/views/ToolbarAuralView.js
@@ -1,70 +1,42 @@
 /**
- * @file
- * A Backbone view for the aural feedback of the toolbar.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/toolbar/js/views/ToolbarAuralView.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function (Backbone, Drupal) {
 
   'use strict';
 
-  Drupal.toolbar.ToolbarAuralView = Backbone.View.extend(/** @lends Drupal.toolbar.ToolbarAuralView# */{
-
-    /**
-     * Backbone view for the aural feedback of the toolbar.
-     *
-     * @constructs
-     *
-     * @augments Backbone.View
-     *
-     * @param {object} options
-     *   Options for the view.
-     * @param {object} options.strings
-     *   Various strings to use in the view.
-     */
-    initialize: function (options) {
+  Drupal.toolbar.ToolbarAuralView = Backbone.View.extend({
+    initialize: function initialize(options) {
       this.strings = options.strings;
 
       this.listenTo(this.model, 'change:orientation', this.onOrientationChange);
       this.listenTo(this.model, 'change:activeTray', this.onActiveTrayChange);
     },
 
-    /**
-     * Announces an orientation change.
-     *
-     * @param {Drupal.toolbar.ToolbarModel} model
-     *   The toolbar model in question.
-     * @param {string} orientation
-     *   The new value of the orientation attribute in the model.
-     */
-    onOrientationChange: function (model, orientation) {
+    onOrientationChange: function onOrientationChange(model, orientation) {
       Drupal.announce(Drupal.t('Tray orientation changed to @orientation.', {
         '@orientation': orientation
       }));
     },
 
-    /**
-     * Announces a changed active tray.
-     *
-     * @param {Drupal.toolbar.ToolbarModel} model
-     *   The toolbar model in question.
-     * @param {HTMLElement} tray
-     *   The new value of the tray attribute in the model.
-     */
-    onActiveTrayChange: function (model, tray) {
-      var relevantTray = (tray === null) ? model.previous('activeTray') : tray;
-      var action = (tray === null) ? Drupal.t('closed') : Drupal.t('opened');
+    onActiveTrayChange: function onActiveTrayChange(model, tray) {
+      var relevantTray = tray === null ? model.previous('activeTray') : tray;
+      var action = tray === null ? Drupal.t('closed') : Drupal.t('opened');
       var trayNameElement = relevantTray.querySelector('.toolbar-tray-name');
       var text;
       if (trayNameElement !== null) {
         text = Drupal.t('Tray "@tray" @action.', {
           '@tray': trayNameElement.textContent, '@action': action
         });
-      }
-      else {
-        text = Drupal.t('Tray @action.', {'@action': action});
+      } else {
+        text = Drupal.t('Tray @action.', { '@action': action });
       }
       Drupal.announce(text);
     }
   });
-
-}(Backbone, Drupal));
+})(Backbone, Drupal);
\ No newline at end of file
diff --git a/core/modules/toolbar/js/views/ToolbarVisualView.es6.js b/core/modules/toolbar/js/views/ToolbarVisualView.es6.js
new file mode 100644
index 000000000000..f26c98c4d933
--- /dev/null
+++ b/core/modules/toolbar/js/views/ToolbarVisualView.es6.js
@@ -0,0 +1,305 @@
+/**
+ * @file
+ * A Backbone view for the toolbar element. Listens to mouse & touch.
+ */
+
+(function ($, Drupal, drupalSettings, Backbone) {
+
+  'use strict';
+
+  Drupal.toolbar.ToolbarVisualView = Backbone.View.extend(/** @lends Drupal.toolbar.ToolbarVisualView# */{
+
+    /**
+     * Event map for the `ToolbarVisualView`.
+     *
+     * @return {object}
+     *   A map of events.
+     */
+    events: function () {
+      // Prevents delay and simulated mouse events.
+      var touchEndToClick = function (event) {
+        event.preventDefault();
+        event.target.click();
+      };
+
+      return {
+        'click .toolbar-bar .toolbar-tab .trigger': 'onTabClick',
+        'click .toolbar-toggle-orientation button': 'onOrientationToggleClick',
+        'touchend .toolbar-bar .toolbar-tab .trigger': touchEndToClick,
+        'touchend .toolbar-toggle-orientation button': touchEndToClick
+      };
+    },
+
+    /**
+     * Backbone view for the toolbar element. Listens to mouse & touch.
+     *
+     * @constructs
+     *
+     * @augments Backbone.View
+     *
+     * @param {object} options
+     *   Options for the view object.
+     * @param {object} options.strings
+     *   Various strings to use in the view.
+     */
+    initialize: function (options) {
+      this.strings = options.strings;
+
+      this.listenTo(this.model, 'change:activeTab change:orientation change:isOriented change:isTrayToggleVisible', this.render);
+      this.listenTo(this.model, 'change:mqMatches', this.onMediaQueryChange);
+      this.listenTo(this.model, 'change:offsets', this.adjustPlacement);
+
+      // Add the tray orientation toggles.
+      this.$el
+        .find('.toolbar-tray .toolbar-lining')
+        .append(Drupal.theme('toolbarOrientationToggle'));
+
+      // Trigger an activeTab change so that listening scripts can respond on
+      // page load. This will call render.
+      this.model.trigger('change:activeTab');
+    },
+
+    /**
+     * @inheritdoc
+     *
+     * @return {Drupal.toolbar.ToolbarVisualView}
+     *   The `ToolbarVisualView` instance.
+     */
+    render: function () {
+      this.updateTabs();
+      this.updateTrayOrientation();
+      this.updateBarAttributes();
+      // Load the subtrees if the orientation of the toolbar is changed to
+      // vertical. This condition responds to the case that the toolbar switches
+      // from horizontal to vertical orientation. The toolbar starts in a
+      // vertical orientation by default and then switches to horizontal during
+      // initialization if the media query conditions are met. Simply checking
+      // that the orientation is vertical here would result in the subtrees
+      // always being loaded, even when the toolbar initialization ultimately
+      // results in a horizontal orientation.
+      //
+      // @see Drupal.behaviors.toolbar.attach() where admin menu subtrees
+      // loading is invoked during initialization after media query conditions
+      // have been processed.
+      if (this.model.changed.orientation === 'vertical' || this.model.changed.activeTab) {
+        this.loadSubtrees();
+      }
+      // Trigger a recalculation of viewport displacing elements. Use setTimeout
+      // to ensure this recalculation happens after changes to visual elements
+      // have processed.
+      window.setTimeout(function () {
+        Drupal.displace(true);
+      }, 0);
+      return this;
+    },
+
+    /**
+     * Responds to a toolbar tab click.
+     *
+     * @param {jQuery.Event} event
+     *   The event triggered.
+     */
+    onTabClick: function (event) {
+      // If this tab has a tray associated with it, it is considered an
+      // activatable tab.
+      if (event.target.hasAttribute('data-toolbar-tray')) {
+        var activeTab = this.model.get('activeTab');
+        var clickedTab = event.target;
+
+        // Set the event target as the active item if it is not already.
+        this.model.set('activeTab', (!activeTab || clickedTab !== activeTab) ? clickedTab : null);
+
+        event.preventDefault();
+        event.stopPropagation();
+      }
+    },
+
+    /**
+     * Toggles the orientation of a toolbar tray.
+     *
+     * @param {jQuery.Event} event
+     *   The event triggered.
+     */
+    onOrientationToggleClick: function (event) {
+      var orientation = this.model.get('orientation');
+      // Determine the toggle-to orientation.
+      var antiOrientation = (orientation === 'vertical') ? 'horizontal' : 'vertical';
+      var locked = antiOrientation === 'vertical';
+      // Remember the locked state.
+      if (locked) {
+        localStorage.setItem('Drupal.toolbar.trayVerticalLocked', 'true');
+      }
+      else {
+        localStorage.removeItem('Drupal.toolbar.trayVerticalLocked');
+      }
+      // Update the model.
+      this.model.set({
+        locked: locked,
+        orientation: antiOrientation
+      }, {
+        validate: true,
+        override: true
+      });
+
+      event.preventDefault();
+      event.stopPropagation();
+    },
+
+    /**
+     * Updates the display of the tabs: toggles a tab and the associated tray.
+     */
+    updateTabs: function () {
+      var $tab = $(this.model.get('activeTab'));
+      // Deactivate the previous tab.
+      $(this.model.previous('activeTab'))
+        .removeClass('is-active')
+        .prop('aria-pressed', false);
+      // Deactivate the previous tray.
+      $(this.model.previous('activeTray'))
+        .removeClass('is-active');
+
+      // Activate the selected tab.
+      if ($tab.length > 0) {
+        $tab
+          .addClass('is-active')
+          // Mark the tab as pressed.
+          .prop('aria-pressed', true);
+        var name = $tab.attr('data-toolbar-tray');
+        // Store the active tab name or remove the setting.
+        var id = $tab.get(0).id;
+        if (id) {
+          localStorage.setItem('Drupal.toolbar.activeTabID', JSON.stringify(id));
+        }
+        // Activate the associated tray.
+        var $tray = this.$el.find('[data-toolbar-tray="' + name + '"].toolbar-tray');
+        if ($tray.length) {
+          $tray.addClass('is-active');
+          this.model.set('activeTray', $tray.get(0));
+        }
+        else {
+          // There is no active tray.
+          this.model.set('activeTray', null);
+        }
+      }
+      else {
+        // There is no active tray.
+        this.model.set('activeTray', null);
+        localStorage.removeItem('Drupal.toolbar.activeTabID');
+      }
+    },
+
+    /**
+     * Update the attributes of the toolbar bar element.
+     */
+    updateBarAttributes: function () {
+      var isOriented = this.model.get('isOriented');
+      if (isOriented) {
+        this.$el.find('.toolbar-bar').attr('data-offset-top', '');
+      }
+      else {
+        this.$el.find('.toolbar-bar').removeAttr('data-offset-top');
+      }
+      // Toggle between a basic vertical view and a more sophisticated
+      // horizontal and vertical display of the toolbar bar and trays.
+      this.$el.toggleClass('toolbar-oriented', isOriented);
+    },
+
+    /**
+     * Updates the orientation of the active tray if necessary.
+     */
+    updateTrayOrientation: function () {
+      var orientation = this.model.get('orientation');
+      // The antiOrientation is used to render the view of action buttons like
+      // the tray orientation toggle.
+      var antiOrientation = (orientation === 'vertical') ? 'horizontal' : 'vertical';
+      // Update the orientation of the trays.
+      var $trays = this.$el.find('.toolbar-tray')
+        .removeClass('toolbar-tray-horizontal toolbar-tray-vertical')
+        .addClass('toolbar-tray-' + orientation);
+
+      // Update the tray orientation toggle button.
+      var iconClass = 'toolbar-icon-toggle-' + orientation;
+      var iconAntiClass = 'toolbar-icon-toggle-' + antiOrientation;
+      var $orientationToggle = this.$el.find('.toolbar-toggle-orientation')
+        .toggle(this.model.get('isTrayToggleVisible'));
+      $orientationToggle.find('button')
+        .val(antiOrientation)
+        .attr('title', this.strings[antiOrientation])
+        .text(this.strings[antiOrientation])
+        .removeClass(iconClass)
+        .addClass(iconAntiClass);
+
+      // Update data offset attributes for the trays.
+      var dir = document.documentElement.dir;
+      var edge = (dir === 'rtl') ? 'right' : 'left';
+      // Remove data-offset attributes from the trays so they can be refreshed.
+      $trays.removeAttr('data-offset-left data-offset-right data-offset-top');
+      // If an active vertical tray exists, mark it as an offset element.
+      $trays.filter('.toolbar-tray-vertical.is-active').attr('data-offset-' + edge, '');
+      // If an active horizontal tray exists, mark it as an offset element.
+      $trays.filter('.toolbar-tray-horizontal.is-active').attr('data-offset-top', '');
+    },
+
+    /**
+     * Sets the tops of the trays so that they align with the bottom of the bar.
+     */
+    adjustPlacement: function () {
+      var $trays = this.$el.find('.toolbar-tray');
+      if (!this.model.get('isOriented')) {
+        $trays.css('margin-top', 0);
+        $trays.removeClass('toolbar-tray-horizontal').addClass('toolbar-tray-vertical');
+      }
+      else {
+        // The toolbar container is invisible. Its placement is used to
+        // determine the container for the trays.
+        $trays.css('margin-top', this.$el.find('.toolbar-bar').outerHeight());
+      }
+    },
+
+    /**
+     * Calls the endpoint URI that builds an AJAX command with the rendered
+     * subtrees.
+     *
+     * The rendered admin menu subtrees HTML is cached on the client in
+     * localStorage until the cache of the admin menu subtrees on the server-
+     * side is invalidated. The subtreesHash is stored in localStorage as well
+     * and compared to the subtreesHash in drupalSettings to determine when the
+     * admin menu subtrees cache has been invalidated.
+     */
+    loadSubtrees: function () {
+      var $activeTab = $(this.model.get('activeTab'));
+      var orientation = this.model.get('orientation');
+      // Only load and render the admin menu subtrees if:
+      //   (1) They have not been loaded yet.
+      //   (2) The active tab is the administration menu tab, indicated by the
+      //       presence of the data-drupal-subtrees attribute.
+      //   (3) The orientation of the tray is vertical.
+      if (!this.model.get('areSubtreesLoaded') && typeof $activeTab.data('drupal-subtrees') !== 'undefined' && orientation === 'vertical') {
+        var subtreesHash = drupalSettings.toolbar.subtreesHash;
+        var theme = drupalSettings.ajaxPageState.theme;
+        var endpoint = Drupal.url('toolbar/subtrees/' + subtreesHash);
+        var cachedSubtreesHash = localStorage.getItem('Drupal.toolbar.subtreesHash.' + theme);
+        var cachedSubtrees = JSON.parse(localStorage.getItem('Drupal.toolbar.subtrees.' + theme));
+        var isVertical = this.model.get('orientation') === 'vertical';
+        // If we have the subtrees in localStorage and the subtree hash has not
+        // changed, then use the cached data.
+        if (isVertical && subtreesHash === cachedSubtreesHash && cachedSubtrees) {
+          Drupal.toolbar.setSubtrees.resolve(cachedSubtrees);
+        }
+        // Only make the call to get the subtrees if the orientation of the
+        // toolbar is vertical.
+        else if (isVertical) {
+          // Remove the cached menu information.
+          localStorage.removeItem('Drupal.toolbar.subtreesHash.' + theme);
+          localStorage.removeItem('Drupal.toolbar.subtrees.' + theme);
+          // The AJAX response's command will trigger the resolve method of the
+          // Drupal.toolbar.setSubtrees Promise.
+          Drupal.ajax({url: endpoint}).execute();
+          // Cache the hash for the subtrees locally.
+          localStorage.setItem('Drupal.toolbar.subtreesHash.' + theme, subtreesHash);
+        }
+      }
+    }
+  });
+
+}(jQuery, Drupal, drupalSettings, Backbone));
diff --git a/core/modules/toolbar/js/views/ToolbarVisualView.js b/core/modules/toolbar/js/views/ToolbarVisualView.js
index f26c98c4d933..330830bd50cd 100644
--- a/core/modules/toolbar/js/views/ToolbarVisualView.js
+++ b/core/modules/toolbar/js/views/ToolbarVisualView.js
@@ -1,23 +1,18 @@
 /**
- * @file
- * A Backbone view for the toolbar element. Listens to mouse & touch.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/toolbar/js/views/ToolbarVisualView.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings, Backbone) {
 
   'use strict';
 
-  Drupal.toolbar.ToolbarVisualView = Backbone.View.extend(/** @lends Drupal.toolbar.ToolbarVisualView# */{
-
-    /**
-     * Event map for the `ToolbarVisualView`.
-     *
-     * @return {object}
-     *   A map of events.
-     */
-    events: function () {
-      // Prevents delay and simulated mouse events.
-      var touchEndToClick = function (event) {
+  Drupal.toolbar.ToolbarVisualView = Backbone.View.extend({
+    events: function events() {
+      var touchEndToClick = function touchEndToClick(event) {
         event.preventDefault();
         event.target.click();
       };
@@ -30,109 +25,57 @@
       };
     },
 
-    /**
-     * Backbone view for the toolbar element. Listens to mouse & touch.
-     *
-     * @constructs
-     *
-     * @augments Backbone.View
-     *
-     * @param {object} options
-     *   Options for the view object.
-     * @param {object} options.strings
-     *   Various strings to use in the view.
-     */
-    initialize: function (options) {
+    initialize: function initialize(options) {
       this.strings = options.strings;
 
       this.listenTo(this.model, 'change:activeTab change:orientation change:isOriented change:isTrayToggleVisible', this.render);
       this.listenTo(this.model, 'change:mqMatches', this.onMediaQueryChange);
       this.listenTo(this.model, 'change:offsets', this.adjustPlacement);
 
-      // Add the tray orientation toggles.
-      this.$el
-        .find('.toolbar-tray .toolbar-lining')
-        .append(Drupal.theme('toolbarOrientationToggle'));
+      this.$el.find('.toolbar-tray .toolbar-lining').append(Drupal.theme('toolbarOrientationToggle'));
 
-      // Trigger an activeTab change so that listening scripts can respond on
-      // page load. This will call render.
       this.model.trigger('change:activeTab');
     },
 
-    /**
-     * @inheritdoc
-     *
-     * @return {Drupal.toolbar.ToolbarVisualView}
-     *   The `ToolbarVisualView` instance.
-     */
-    render: function () {
+    render: function render() {
       this.updateTabs();
       this.updateTrayOrientation();
       this.updateBarAttributes();
-      // Load the subtrees if the orientation of the toolbar is changed to
-      // vertical. This condition responds to the case that the toolbar switches
-      // from horizontal to vertical orientation. The toolbar starts in a
-      // vertical orientation by default and then switches to horizontal during
-      // initialization if the media query conditions are met. Simply checking
-      // that the orientation is vertical here would result in the subtrees
-      // always being loaded, even when the toolbar initialization ultimately
-      // results in a horizontal orientation.
-      //
-      // @see Drupal.behaviors.toolbar.attach() where admin menu subtrees
-      // loading is invoked during initialization after media query conditions
-      // have been processed.
+
       if (this.model.changed.orientation === 'vertical' || this.model.changed.activeTab) {
         this.loadSubtrees();
       }
-      // Trigger a recalculation of viewport displacing elements. Use setTimeout
-      // to ensure this recalculation happens after changes to visual elements
-      // have processed.
+
       window.setTimeout(function () {
         Drupal.displace(true);
       }, 0);
       return this;
     },
 
-    /**
-     * Responds to a toolbar tab click.
-     *
-     * @param {jQuery.Event} event
-     *   The event triggered.
-     */
-    onTabClick: function (event) {
-      // If this tab has a tray associated with it, it is considered an
-      // activatable tab.
+    onTabClick: function onTabClick(event) {
       if (event.target.hasAttribute('data-toolbar-tray')) {
         var activeTab = this.model.get('activeTab');
         var clickedTab = event.target;
 
-        // Set the event target as the active item if it is not already.
-        this.model.set('activeTab', (!activeTab || clickedTab !== activeTab) ? clickedTab : null);
+        this.model.set('activeTab', !activeTab || clickedTab !== activeTab ? clickedTab : null);
 
         event.preventDefault();
         event.stopPropagation();
       }
     },
 
-    /**
-     * Toggles the orientation of a toolbar tray.
-     *
-     * @param {jQuery.Event} event
-     *   The event triggered.
-     */
-    onOrientationToggleClick: function (event) {
+    onOrientationToggleClick: function onOrientationToggleClick(event) {
       var orientation = this.model.get('orientation');
-      // Determine the toggle-to orientation.
-      var antiOrientation = (orientation === 'vertical') ? 'horizontal' : 'vertical';
+
+      var antiOrientation = orientation === 'vertical' ? 'horizontal' : 'vertical';
       var locked = antiOrientation === 'vertical';
-      // Remember the locked state.
+
       if (locked) {
         localStorage.setItem('Drupal.toolbar.trayVerticalLocked', 'true');
-      }
-      else {
+      } else {
         localStorage.removeItem('Drupal.toolbar.trayVerticalLocked');
       }
-      // Update the model.
+
       this.model.set({
         locked: locked,
         orientation: antiOrientation
@@ -145,135 +88,82 @@
       event.stopPropagation();
     },
 
-    /**
-     * Updates the display of the tabs: toggles a tab and the associated tray.
-     */
-    updateTabs: function () {
+    updateTabs: function updateTabs() {
       var $tab = $(this.model.get('activeTab'));
-      // Deactivate the previous tab.
-      $(this.model.previous('activeTab'))
-        .removeClass('is-active')
-        .prop('aria-pressed', false);
-      // Deactivate the previous tray.
-      $(this.model.previous('activeTray'))
-        .removeClass('is-active');
-
-      // Activate the selected tab.
+
+      $(this.model.previous('activeTab')).removeClass('is-active').prop('aria-pressed', false);
+
+      $(this.model.previous('activeTray')).removeClass('is-active');
+
       if ($tab.length > 0) {
-        $tab
-          .addClass('is-active')
-          // Mark the tab as pressed.
-          .prop('aria-pressed', true);
+        $tab.addClass('is-active').prop('aria-pressed', true);
         var name = $tab.attr('data-toolbar-tray');
-        // Store the active tab name or remove the setting.
+
         var id = $tab.get(0).id;
         if (id) {
           localStorage.setItem('Drupal.toolbar.activeTabID', JSON.stringify(id));
         }
-        // Activate the associated tray.
+
         var $tray = this.$el.find('[data-toolbar-tray="' + name + '"].toolbar-tray');
         if ($tray.length) {
           $tray.addClass('is-active');
           this.model.set('activeTray', $tray.get(0));
-        }
-        else {
-          // There is no active tray.
+        } else {
           this.model.set('activeTray', null);
         }
-      }
-      else {
-        // There is no active tray.
+      } else {
         this.model.set('activeTray', null);
         localStorage.removeItem('Drupal.toolbar.activeTabID');
       }
     },
 
-    /**
-     * Update the attributes of the toolbar bar element.
-     */
-    updateBarAttributes: function () {
+    updateBarAttributes: function updateBarAttributes() {
       var isOriented = this.model.get('isOriented');
       if (isOriented) {
         this.$el.find('.toolbar-bar').attr('data-offset-top', '');
-      }
-      else {
+      } else {
         this.$el.find('.toolbar-bar').removeAttr('data-offset-top');
       }
-      // Toggle between a basic vertical view and a more sophisticated
-      // horizontal and vertical display of the toolbar bar and trays.
+
       this.$el.toggleClass('toolbar-oriented', isOriented);
     },
 
-    /**
-     * Updates the orientation of the active tray if necessary.
-     */
-    updateTrayOrientation: function () {
+    updateTrayOrientation: function updateTrayOrientation() {
       var orientation = this.model.get('orientation');
-      // The antiOrientation is used to render the view of action buttons like
-      // the tray orientation toggle.
-      var antiOrientation = (orientation === 'vertical') ? 'horizontal' : 'vertical';
-      // Update the orientation of the trays.
-      var $trays = this.$el.find('.toolbar-tray')
-        .removeClass('toolbar-tray-horizontal toolbar-tray-vertical')
-        .addClass('toolbar-tray-' + orientation);
-
-      // Update the tray orientation toggle button.
+
+      var antiOrientation = orientation === 'vertical' ? 'horizontal' : 'vertical';
+
+      var $trays = this.$el.find('.toolbar-tray').removeClass('toolbar-tray-horizontal toolbar-tray-vertical').addClass('toolbar-tray-' + orientation);
+
       var iconClass = 'toolbar-icon-toggle-' + orientation;
       var iconAntiClass = 'toolbar-icon-toggle-' + antiOrientation;
-      var $orientationToggle = this.$el.find('.toolbar-toggle-orientation')
-        .toggle(this.model.get('isTrayToggleVisible'));
-      $orientationToggle.find('button')
-        .val(antiOrientation)
-        .attr('title', this.strings[antiOrientation])
-        .text(this.strings[antiOrientation])
-        .removeClass(iconClass)
-        .addClass(iconAntiClass);
-
-      // Update data offset attributes for the trays.
+      var $orientationToggle = this.$el.find('.toolbar-toggle-orientation').toggle(this.model.get('isTrayToggleVisible'));
+      $orientationToggle.find('button').val(antiOrientation).attr('title', this.strings[antiOrientation]).text(this.strings[antiOrientation]).removeClass(iconClass).addClass(iconAntiClass);
+
       var dir = document.documentElement.dir;
-      var edge = (dir === 'rtl') ? 'right' : 'left';
-      // Remove data-offset attributes from the trays so they can be refreshed.
+      var edge = dir === 'rtl' ? 'right' : 'left';
+
       $trays.removeAttr('data-offset-left data-offset-right data-offset-top');
-      // If an active vertical tray exists, mark it as an offset element.
+
       $trays.filter('.toolbar-tray-vertical.is-active').attr('data-offset-' + edge, '');
-      // If an active horizontal tray exists, mark it as an offset element.
+
       $trays.filter('.toolbar-tray-horizontal.is-active').attr('data-offset-top', '');
     },
 
-    /**
-     * Sets the tops of the trays so that they align with the bottom of the bar.
-     */
-    adjustPlacement: function () {
+    adjustPlacement: function adjustPlacement() {
       var $trays = this.$el.find('.toolbar-tray');
       if (!this.model.get('isOriented')) {
         $trays.css('margin-top', 0);
         $trays.removeClass('toolbar-tray-horizontal').addClass('toolbar-tray-vertical');
-      }
-      else {
-        // The toolbar container is invisible. Its placement is used to
-        // determine the container for the trays.
+      } else {
         $trays.css('margin-top', this.$el.find('.toolbar-bar').outerHeight());
       }
     },
 
-    /**
-     * Calls the endpoint URI that builds an AJAX command with the rendered
-     * subtrees.
-     *
-     * The rendered admin menu subtrees HTML is cached on the client in
-     * localStorage until the cache of the admin menu subtrees on the server-
-     * side is invalidated. The subtreesHash is stored in localStorage as well
-     * and compared to the subtreesHash in drupalSettings to determine when the
-     * admin menu subtrees cache has been invalidated.
-     */
-    loadSubtrees: function () {
+    loadSubtrees: function loadSubtrees() {
       var $activeTab = $(this.model.get('activeTab'));
       var orientation = this.model.get('orientation');
-      // Only load and render the admin menu subtrees if:
-      //   (1) They have not been loaded yet.
-      //   (2) The active tab is the administration menu tab, indicated by the
-      //       presence of the data-drupal-subtrees attribute.
-      //   (3) The orientation of the tray is vertical.
+
       if (!this.model.get('areSubtreesLoaded') && typeof $activeTab.data('drupal-subtrees') !== 'undefined' && orientation === 'vertical') {
         var subtreesHash = drupalSettings.toolbar.subtreesHash;
         var theme = drupalSettings.ajaxPageState.theme;
@@ -281,25 +171,18 @@
         var cachedSubtreesHash = localStorage.getItem('Drupal.toolbar.subtreesHash.' + theme);
         var cachedSubtrees = JSON.parse(localStorage.getItem('Drupal.toolbar.subtrees.' + theme));
         var isVertical = this.model.get('orientation') === 'vertical';
-        // If we have the subtrees in localStorage and the subtree hash has not
-        // changed, then use the cached data.
+
         if (isVertical && subtreesHash === cachedSubtreesHash && cachedSubtrees) {
           Drupal.toolbar.setSubtrees.resolve(cachedSubtrees);
-        }
-        // Only make the call to get the subtrees if the orientation of the
-        // toolbar is vertical.
-        else if (isVertical) {
-          // Remove the cached menu information.
-          localStorage.removeItem('Drupal.toolbar.subtreesHash.' + theme);
-          localStorage.removeItem('Drupal.toolbar.subtrees.' + theme);
-          // The AJAX response's command will trigger the resolve method of the
-          // Drupal.toolbar.setSubtrees Promise.
-          Drupal.ajax({url: endpoint}).execute();
-          // Cache the hash for the subtrees locally.
-          localStorage.setItem('Drupal.toolbar.subtreesHash.' + theme, subtreesHash);
-        }
+        } else if (isVertical) {
+            localStorage.removeItem('Drupal.toolbar.subtreesHash.' + theme);
+            localStorage.removeItem('Drupal.toolbar.subtrees.' + theme);
+
+            Drupal.ajax({ url: endpoint }).execute();
+
+            localStorage.setItem('Drupal.toolbar.subtreesHash.' + theme, subtreesHash);
+          }
       }
     }
   });
-
-}(jQuery, Drupal, drupalSettings, Backbone));
+})(jQuery, Drupal, drupalSettings, Backbone);
\ No newline at end of file
diff --git a/core/modules/tour/js/tour.es6.js b/core/modules/tour/js/tour.es6.js
new file mode 100644
index 000000000000..2c7050f75fd0
--- /dev/null
+++ b/core/modules/tour/js/tour.es6.js
@@ -0,0 +1,270 @@
+/**
+ * @file
+ * Attaches behaviors for the Tour module's toolbar tab.
+ */
+
+(function ($, Backbone, Drupal, document) {
+
+  'use strict';
+
+  var queryString = decodeURI(window.location.search);
+
+  /**
+   * Attaches the tour's toolbar tab behavior.
+   *
+   * It uses the query string for:
+   * - tour: When ?tour=1 is present, the tour will start automatically after
+   *   the page has loaded.
+   * - tips: Pass ?tips=class in the url to filter the available tips to the
+   *   subset which match the given class.
+   *
+   * @example
+   * http://example.com/foo?tour=1&tips=bar
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attach tour functionality on `tour` events.
+   */
+  Drupal.behaviors.tour = {
+    attach: function (context) {
+      $('body').once('tour').each(function () {
+        var model = new Drupal.tour.models.StateModel();
+        new Drupal.tour.views.ToggleTourView({
+          el: $(context).find('#toolbar-tab-tour'),
+          model: model
+        });
+
+        model
+          // Allow other scripts to respond to tour events.
+          .on('change:isActive', function (model, isActive) {
+            $(document).trigger((isActive) ? 'drupalTourStarted' : 'drupalTourStopped');
+          })
+          // Initialization: check whether a tour is available on the current
+          // page.
+          .set('tour', $(context).find('ol#tour'));
+
+        // Start the tour immediately if toggled via query string.
+        if (/tour=?/i.test(queryString)) {
+          model.set('isActive', true);
+        }
+      });
+    }
+  };
+
+  /**
+   * @namespace
+   */
+  Drupal.tour = Drupal.tour || {
+
+    /**
+     * @namespace Drupal.tour.models
+     */
+    models: {},
+
+    /**
+     * @namespace Drupal.tour.views
+     */
+    views: {}
+  };
+
+  /**
+   * Backbone Model for tours.
+   *
+   * @constructor
+   *
+   * @augments Backbone.Model
+   */
+  Drupal.tour.models.StateModel = Backbone.Model.extend(/** @lends Drupal.tour.models.StateModel# */{
+
+    /**
+     * @type {object}
+     */
+    defaults: /** @lends Drupal.tour.models.StateModel# */{
+
+      /**
+       * Indicates whether the Drupal root window has a tour.
+       *
+       * @type {Array}
+       */
+      tour: [],
+
+      /**
+       * Indicates whether the tour is currently running.
+       *
+       * @type {bool}
+       */
+      isActive: false,
+
+      /**
+       * Indicates which tour is the active one (necessary to cleanly stop).
+       *
+       * @type {Array}
+       */
+      activeTour: []
+    }
+  });
+
+  Drupal.tour.views.ToggleTourView = Backbone.View.extend(/** @lends Drupal.tour.views.ToggleTourView# */{
+
+    /**
+     * @type {object}
+     */
+    events: {click: 'onClick'},
+
+    /**
+     * Handles edit mode toggle interactions.
+     *
+     * @constructs
+     *
+     * @augments Backbone.View
+     */
+    initialize: function () {
+      this.listenTo(this.model, 'change:tour change:isActive', this.render);
+      this.listenTo(this.model, 'change:isActive', this.toggleTour);
+    },
+
+    /**
+     * @inheritdoc
+     *
+     * @return {Drupal.tour.views.ToggleTourView}
+     *   The `ToggleTourView` view.
+     */
+    render: function () {
+      // Render the visibility.
+      this.$el.toggleClass('hidden', this._getTour().length === 0);
+      // Render the state.
+      var isActive = this.model.get('isActive');
+      this.$el.find('button')
+        .toggleClass('is-active', isActive)
+        .prop('aria-pressed', isActive);
+      return this;
+    },
+
+    /**
+     * Model change handler; starts or stops the tour.
+     */
+    toggleTour: function () {
+      if (this.model.get('isActive')) {
+        var $tour = this._getTour();
+        this._removeIrrelevantTourItems($tour, this._getDocument());
+        var that = this;
+        if ($tour.find('li').length) {
+          $tour.joyride({
+            autoStart: true,
+            postRideCallback: function () { that.model.set('isActive', false); },
+            // HTML segments for tip layout.
+            template: {
+              link: '<a href=\"#close\" class=\"joyride-close-tip\">&times;</a>',
+              button: '<a href=\"#\" class=\"button button--primary joyride-next-tip\"></a>'
+            }
+          });
+          this.model.set({isActive: true, activeTour: $tour});
+        }
+      }
+      else {
+        this.model.get('activeTour').joyride('destroy');
+        this.model.set({isActive: false, activeTour: []});
+      }
+    },
+
+    /**
+     * Toolbar tab click event handler; toggles isActive.
+     *
+     * @param {jQuery.Event} event
+     *   The click event.
+     */
+    onClick: function (event) {
+      this.model.set('isActive', !this.model.get('isActive'));
+      event.preventDefault();
+      event.stopPropagation();
+    },
+
+    /**
+     * Gets the tour.
+     *
+     * @return {jQuery}
+     *   A jQuery element pointing to a `<ol>` containing tour items.
+     */
+    _getTour: function () {
+      return this.model.get('tour');
+    },
+
+    /**
+     * Gets the relevant document as a jQuery element.
+     *
+     * @return {jQuery}
+     *   A jQuery element pointing to the document within which a tour would be
+     *   started given the current state.
+     */
+    _getDocument: function () {
+      return $(document);
+    },
+
+    /**
+     * Removes tour items for elements that don't have matching page elements.
+     *
+     * Or that are explicitly filtered out via the 'tips' query string.
+     *
+     * @example
+     * <caption>This will filter out tips that do not have a matching
+     * page element or don't have the "bar" class.</caption>
+     * http://example.com/foo?tips=bar
+     *
+     * @param {jQuery} $tour
+     *   A jQuery element pointing to a `<ol>` containing tour items.
+     * @param {jQuery} $document
+     *   A jQuery element pointing to the document within which the elements
+     *   should be sought.
+     *
+     * @see Drupal.tour.views.ToggleTourView#_getDocument
+     */
+    _removeIrrelevantTourItems: function ($tour, $document) {
+      var removals = false;
+      var tips = /tips=([^&]+)/.exec(queryString);
+      $tour
+        .find('li')
+        .each(function () {
+          var $this = $(this);
+          var itemId = $this.attr('data-id');
+          var itemClass = $this.attr('data-class');
+          // If the query parameter 'tips' is set, remove all tips that don't
+          // have the matching class.
+          if (tips && !$(this).hasClass(tips[1])) {
+            removals = true;
+            $this.remove();
+            return;
+          }
+          // Remove tip from the DOM if there is no corresponding page element.
+          if ((!itemId && !itemClass) ||
+            (itemId && $document.find('#' + itemId).length) ||
+            (itemClass && $document.find('.' + itemClass).length)) {
+            return;
+          }
+          removals = true;
+          $this.remove();
+        });
+
+      // If there were removals, we'll have to do some clean-up.
+      if (removals) {
+        var total = $tour.find('li').length;
+        if (!total) {
+          this.model.set({tour: []});
+        }
+
+        $tour
+          .find('li')
+          // Rebuild the progress data.
+          .each(function (index) {
+            var progress = Drupal.t('!tour_item of !total', {'!tour_item': index + 1, '!total': total});
+            $(this).find('.tour-progress').text(progress);
+          })
+          // Update the last item to have "End tour" as the button.
+          .eq(-1)
+          .attr('data-text', Drupal.t('End tour'));
+      }
+    }
+
+  });
+
+})(jQuery, Backbone, Drupal, document);
diff --git a/core/modules/tour/js/tour.js b/core/modules/tour/js/tour.js
index 2c7050f75fd0..59c0278f54b4 100644
--- a/core/modules/tour/js/tour.js
+++ b/core/modules/tour/js/tour.js
@@ -1,7 +1,10 @@
 /**
- * @file
- * Attaches behaviors for the Tour module's toolbar tab.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/tour/js/tour.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Backbone, Drupal, document) {
 
@@ -9,25 +12,8 @@
 
   var queryString = decodeURI(window.location.search);
 
-  /**
-   * Attaches the tour's toolbar tab behavior.
-   *
-   * It uses the query string for:
-   * - tour: When ?tour=1 is present, the tour will start automatically after
-   *   the page has loaded.
-   * - tips: Pass ?tips=class in the url to filter the available tips to the
-   *   subset which match the given class.
-   *
-   * @example
-   * http://example.com/foo?tour=1&tips=bar
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attach tour functionality on `tour` events.
-   */
   Drupal.behaviors.tour = {
-    attach: function (context) {
+    attach: function attach(context) {
       $('body').once('tour').each(function () {
         var model = new Drupal.tour.models.StateModel();
         new Drupal.tour.views.ToggleTourView({
@@ -35,16 +21,10 @@
           model: model
         });
 
-        model
-          // Allow other scripts to respond to tour events.
-          .on('change:isActive', function (model, isActive) {
-            $(document).trigger((isActive) ? 'drupalTourStarted' : 'drupalTourStopped');
-          })
-          // Initialization: check whether a tour is available on the current
-          // page.
-          .set('tour', $(context).find('ol#tour'));
+        model.on('change:isActive', function (model, isActive) {
+          $(document).trigger(isActive ? 'drupalTourStarted' : 'drupalTourStopped');
+        }).set('tour', $(context).find('ol#tour'));
 
-        // Start the tour immediately if toggled via query string.
         if (/tour=?/i.test(queryString)) {
           model.set('isActive', true);
         }
@@ -52,99 +32,39 @@
     }
   };
 
-  /**
-   * @namespace
-   */
   Drupal.tour = Drupal.tour || {
-
-    /**
-     * @namespace Drupal.tour.models
-     */
     models: {},
 
-    /**
-     * @namespace Drupal.tour.views
-     */
     views: {}
   };
 
-  /**
-   * Backbone Model for tours.
-   *
-   * @constructor
-   *
-   * @augments Backbone.Model
-   */
-  Drupal.tour.models.StateModel = Backbone.Model.extend(/** @lends Drupal.tour.models.StateModel# */{
-
-    /**
-     * @type {object}
-     */
-    defaults: /** @lends Drupal.tour.models.StateModel# */{
-
-      /**
-       * Indicates whether the Drupal root window has a tour.
-       *
-       * @type {Array}
-       */
+  Drupal.tour.models.StateModel = Backbone.Model.extend({
+    defaults: {
       tour: [],
 
-      /**
-       * Indicates whether the tour is currently running.
-       *
-       * @type {bool}
-       */
       isActive: false,
 
-      /**
-       * Indicates which tour is the active one (necessary to cleanly stop).
-       *
-       * @type {Array}
-       */
       activeTour: []
     }
   });
 
-  Drupal.tour.views.ToggleTourView = Backbone.View.extend(/** @lends Drupal.tour.views.ToggleTourView# */{
-
-    /**
-     * @type {object}
-     */
-    events: {click: 'onClick'},
+  Drupal.tour.views.ToggleTourView = Backbone.View.extend({
+    events: { click: 'onClick' },
 
-    /**
-     * Handles edit mode toggle interactions.
-     *
-     * @constructs
-     *
-     * @augments Backbone.View
-     */
-    initialize: function () {
+    initialize: function initialize() {
       this.listenTo(this.model, 'change:tour change:isActive', this.render);
       this.listenTo(this.model, 'change:isActive', this.toggleTour);
     },
 
-    /**
-     * @inheritdoc
-     *
-     * @return {Drupal.tour.views.ToggleTourView}
-     *   The `ToggleTourView` view.
-     */
-    render: function () {
-      // Render the visibility.
+    render: function render() {
       this.$el.toggleClass('hidden', this._getTour().length === 0);
-      // Render the state.
+
       var isActive = this.model.get('isActive');
-      this.$el.find('button')
-        .toggleClass('is-active', isActive)
-        .prop('aria-pressed', isActive);
+      this.$el.find('button').toggleClass('is-active', isActive).prop('aria-pressed', isActive);
       return this;
     },
 
-    /**
-     * Model change handler; starts or stops the tour.
-     */
-    toggleTour: function () {
+    toggleTour: function toggleTour() {
       if (this.model.get('isActive')) {
         var $tour = this._getTour();
         this._removeIrrelevantTourItems($tour, this._getDocument());
@@ -152,119 +72,70 @@
         if ($tour.find('li').length) {
           $tour.joyride({
             autoStart: true,
-            postRideCallback: function () { that.model.set('isActive', false); },
-            // HTML segments for tip layout.
+            postRideCallback: function postRideCallback() {
+              that.model.set('isActive', false);
+            },
+
             template: {
               link: '<a href=\"#close\" class=\"joyride-close-tip\">&times;</a>',
               button: '<a href=\"#\" class=\"button button--primary joyride-next-tip\"></a>'
             }
           });
-          this.model.set({isActive: true, activeTour: $tour});
+          this.model.set({ isActive: true, activeTour: $tour });
         }
-      }
-      else {
+      } else {
         this.model.get('activeTour').joyride('destroy');
-        this.model.set({isActive: false, activeTour: []});
+        this.model.set({ isActive: false, activeTour: [] });
       }
     },
 
-    /**
-     * Toolbar tab click event handler; toggles isActive.
-     *
-     * @param {jQuery.Event} event
-     *   The click event.
-     */
-    onClick: function (event) {
+    onClick: function onClick(event) {
       this.model.set('isActive', !this.model.get('isActive'));
       event.preventDefault();
       event.stopPropagation();
     },
 
-    /**
-     * Gets the tour.
-     *
-     * @return {jQuery}
-     *   A jQuery element pointing to a `<ol>` containing tour items.
-     */
-    _getTour: function () {
+    _getTour: function _getTour() {
       return this.model.get('tour');
     },
 
-    /**
-     * Gets the relevant document as a jQuery element.
-     *
-     * @return {jQuery}
-     *   A jQuery element pointing to the document within which a tour would be
-     *   started given the current state.
-     */
-    _getDocument: function () {
+    _getDocument: function _getDocument() {
       return $(document);
     },
 
-    /**
-     * Removes tour items for elements that don't have matching page elements.
-     *
-     * Or that are explicitly filtered out via the 'tips' query string.
-     *
-     * @example
-     * <caption>This will filter out tips that do not have a matching
-     * page element or don't have the "bar" class.</caption>
-     * http://example.com/foo?tips=bar
-     *
-     * @param {jQuery} $tour
-     *   A jQuery element pointing to a `<ol>` containing tour items.
-     * @param {jQuery} $document
-     *   A jQuery element pointing to the document within which the elements
-     *   should be sought.
-     *
-     * @see Drupal.tour.views.ToggleTourView#_getDocument
-     */
-    _removeIrrelevantTourItems: function ($tour, $document) {
+    _removeIrrelevantTourItems: function _removeIrrelevantTourItems($tour, $document) {
       var removals = false;
       var tips = /tips=([^&]+)/.exec(queryString);
-      $tour
-        .find('li')
-        .each(function () {
-          var $this = $(this);
-          var itemId = $this.attr('data-id');
-          var itemClass = $this.attr('data-class');
-          // If the query parameter 'tips' is set, remove all tips that don't
-          // have the matching class.
-          if (tips && !$(this).hasClass(tips[1])) {
-            removals = true;
-            $this.remove();
-            return;
-          }
-          // Remove tip from the DOM if there is no corresponding page element.
-          if ((!itemId && !itemClass) ||
-            (itemId && $document.find('#' + itemId).length) ||
-            (itemClass && $document.find('.' + itemClass).length)) {
-            return;
-          }
+      $tour.find('li').each(function () {
+        var $this = $(this);
+        var itemId = $this.attr('data-id');
+        var itemClass = $this.attr('data-class');
+
+        if (tips && !$(this).hasClass(tips[1])) {
           removals = true;
           $this.remove();
-        });
+          return;
+        }
+
+        if (!itemId && !itemClass || itemId && $document.find('#' + itemId).length || itemClass && $document.find('.' + itemClass).length) {
+          return;
+        }
+        removals = true;
+        $this.remove();
+      });
 
-      // If there were removals, we'll have to do some clean-up.
       if (removals) {
         var total = $tour.find('li').length;
         if (!total) {
-          this.model.set({tour: []});
+          this.model.set({ tour: [] });
         }
 
-        $tour
-          .find('li')
-          // Rebuild the progress data.
-          .each(function (index) {
-            var progress = Drupal.t('!tour_item of !total', {'!tour_item': index + 1, '!total': total});
-            $(this).find('.tour-progress').text(progress);
-          })
-          // Update the last item to have "End tour" as the button.
-          .eq(-1)
-          .attr('data-text', Drupal.t('End tour'));
+        $tour.find('li').each(function (index) {
+          var progress = Drupal.t('!tour_item of !total', { '!tour_item': index + 1, '!total': total });
+          $(this).find('.tour-progress').text(progress);
+        }).eq(-1).attr('data-text', Drupal.t('End tour'));
       }
     }
 
   });
-
-})(jQuery, Backbone, Drupal, document);
+})(jQuery, Backbone, Drupal, document);
\ No newline at end of file
diff --git a/core/modules/tracker/js/tracker-history.es6.js b/core/modules/tracker/js/tracker-history.es6.js
new file mode 100644
index 000000000000..877a9caec71b
--- /dev/null
+++ b/core/modules/tracker/js/tracker-history.es6.js
@@ -0,0 +1,122 @@
+/**
+ * Attaches behaviors for the Tracker module's History module integration.
+ *
+ * May only be loaded for authenticated users, with the History module enabled.
+ */
+(function ($, Drupal, window) {
+
+  'use strict';
+
+  /**
+   * Render "new" and "updated" node indicators, as well as "X new" replies links.
+   */
+  Drupal.behaviors.trackerHistory = {
+    attach: function (context) {
+      // Find all "new" comment indicator placeholders newer than 30 days ago that
+      // have not already been read after their last comment timestamp.
+      var nodeIDs = [];
+      var $nodeNewPlaceholders = $(context)
+        .find('[data-history-node-timestamp]')
+        .once('history')
+        .filter(function () {
+          var nodeTimestamp = parseInt(this.getAttribute('data-history-node-timestamp'), 10);
+          var nodeID = this.getAttribute('data-history-node-id');
+          if (Drupal.history.needsServerCheck(nodeID, nodeTimestamp)) {
+            nodeIDs.push(nodeID);
+            return true;
+          }
+          else {
+            return false;
+          }
+        });
+
+      // Find all "new" comment indicator placeholders newer than 30 days ago that
+      // have not already been read after their last comment timestamp.
+      var $newRepliesPlaceholders = $(context)
+        .find('[data-history-node-last-comment-timestamp]')
+        .once('history')
+        .filter(function () {
+          var lastCommentTimestamp = parseInt(this.getAttribute('data-history-node-last-comment-timestamp'), 10);
+          var nodeTimestamp = parseInt(this.previousSibling.previousSibling.getAttribute('data-history-node-timestamp'), 10);
+          // Discard placeholders that have zero comments.
+          if (lastCommentTimestamp === nodeTimestamp) {
+            return false;
+          }
+          var nodeID = this.previousSibling.previousSibling.getAttribute('data-history-node-id');
+          if (Drupal.history.needsServerCheck(nodeID, lastCommentTimestamp)) {
+            if (nodeIDs.indexOf(nodeID) === -1) {
+              nodeIDs.push(nodeID);
+            }
+            return true;
+          }
+          else {
+            return false;
+          }
+        });
+
+      if ($nodeNewPlaceholders.length === 0 && $newRepliesPlaceholders.length === 0) {
+        return;
+      }
+
+      // Fetch the node read timestamps from the server.
+      Drupal.history.fetchTimestamps(nodeIDs, function () {
+        processNodeNewIndicators($nodeNewPlaceholders);
+        processNewRepliesIndicators($newRepliesPlaceholders);
+      });
+    }
+  };
+
+  function processNodeNewIndicators($placeholders) {
+    var newNodeString = Drupal.t('new');
+    var updatedNodeString = Drupal.t('updated');
+
+    $placeholders.each(function (index, placeholder) {
+      var timestamp = parseInt(placeholder.getAttribute('data-history-node-timestamp'), 10);
+      var nodeID = placeholder.getAttribute('data-history-node-id');
+      var lastViewTimestamp = Drupal.history.getLastRead(nodeID);
+
+      if (timestamp > lastViewTimestamp) {
+        var message = (lastViewTimestamp === 0) ? newNodeString : updatedNodeString;
+        $(placeholder).append('<span class="marker">' + message + '</span>');
+      }
+    });
+  }
+
+  function processNewRepliesIndicators($placeholders) {
+    // Figure out which placeholders need the "x new" replies links.
+    var placeholdersToUpdate = {};
+    $placeholders.each(function (index, placeholder) {
+      var timestamp = parseInt(placeholder.getAttribute('data-history-node-last-comment-timestamp'), 10);
+      var nodeID = placeholder.previousSibling.previousSibling.getAttribute('data-history-node-id');
+      var lastViewTimestamp = Drupal.history.getLastRead(nodeID);
+
+      // Queue this placeholder's "X new" replies link to be downloaded from the
+      // server.
+      if (timestamp > lastViewTimestamp) {
+        placeholdersToUpdate[nodeID] = placeholder;
+      }
+    });
+
+    // Perform an AJAX request to retrieve node view timestamps.
+    var nodeIDs = Object.keys(placeholdersToUpdate);
+    if (nodeIDs.length === 0) {
+      return;
+    }
+    $.ajax({
+      url: Drupal.url('comments/render_new_comments_node_links'),
+      type: 'POST',
+      data: {'node_ids[]': nodeIDs},
+      dataType: 'json',
+      success: function (results) {
+        for (var nodeID in results) {
+          if (results.hasOwnProperty(nodeID) && placeholdersToUpdate.hasOwnProperty(nodeID)) {
+            var url = results[nodeID].first_new_comment_link;
+            var text = Drupal.formatPlural(results[nodeID].new_comment_count, '1 new', '@count new');
+            $(placeholdersToUpdate[nodeID]).append('<br /><a href="' + url + '">' + text + '</a>');
+          }
+        }
+      }
+    });
+  }
+
+})(jQuery, Drupal, window);
diff --git a/core/modules/tracker/js/tracker-history.js b/core/modules/tracker/js/tracker-history.js
index 877a9caec71b..386c07c2134e 100644
--- a/core/modules/tracker/js/tracker-history.js
+++ b/core/modules/tracker/js/tracker-history.js
@@ -1,64 +1,51 @@
 /**
- * Attaches behaviors for the Tracker module's History module integration.
- *
- * May only be loaded for authenticated users, with the History module enabled.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/tracker/js/tracker-history.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
+
 (function ($, Drupal, window) {
 
   'use strict';
 
-  /**
-   * Render "new" and "updated" node indicators, as well as "X new" replies links.
-   */
   Drupal.behaviors.trackerHistory = {
-    attach: function (context) {
-      // Find all "new" comment indicator placeholders newer than 30 days ago that
-      // have not already been read after their last comment timestamp.
+    attach: function attach(context) {
       var nodeIDs = [];
-      var $nodeNewPlaceholders = $(context)
-        .find('[data-history-node-timestamp]')
-        .once('history')
-        .filter(function () {
-          var nodeTimestamp = parseInt(this.getAttribute('data-history-node-timestamp'), 10);
-          var nodeID = this.getAttribute('data-history-node-id');
-          if (Drupal.history.needsServerCheck(nodeID, nodeTimestamp)) {
-            nodeIDs.push(nodeID);
-            return true;
-          }
-          else {
-            return false;
-          }
-        });
+      var $nodeNewPlaceholders = $(context).find('[data-history-node-timestamp]').once('history').filter(function () {
+        var nodeTimestamp = parseInt(this.getAttribute('data-history-node-timestamp'), 10);
+        var nodeID = this.getAttribute('data-history-node-id');
+        if (Drupal.history.needsServerCheck(nodeID, nodeTimestamp)) {
+          nodeIDs.push(nodeID);
+          return true;
+        } else {
+          return false;
+        }
+      });
 
-      // Find all "new" comment indicator placeholders newer than 30 days ago that
-      // have not already been read after their last comment timestamp.
-      var $newRepliesPlaceholders = $(context)
-        .find('[data-history-node-last-comment-timestamp]')
-        .once('history')
-        .filter(function () {
-          var lastCommentTimestamp = parseInt(this.getAttribute('data-history-node-last-comment-timestamp'), 10);
-          var nodeTimestamp = parseInt(this.previousSibling.previousSibling.getAttribute('data-history-node-timestamp'), 10);
-          // Discard placeholders that have zero comments.
-          if (lastCommentTimestamp === nodeTimestamp) {
-            return false;
-          }
-          var nodeID = this.previousSibling.previousSibling.getAttribute('data-history-node-id');
-          if (Drupal.history.needsServerCheck(nodeID, lastCommentTimestamp)) {
-            if (nodeIDs.indexOf(nodeID) === -1) {
-              nodeIDs.push(nodeID);
-            }
-            return true;
-          }
-          else {
-            return false;
+      var $newRepliesPlaceholders = $(context).find('[data-history-node-last-comment-timestamp]').once('history').filter(function () {
+        var lastCommentTimestamp = parseInt(this.getAttribute('data-history-node-last-comment-timestamp'), 10);
+        var nodeTimestamp = parseInt(this.previousSibling.previousSibling.getAttribute('data-history-node-timestamp'), 10);
+
+        if (lastCommentTimestamp === nodeTimestamp) {
+          return false;
+        }
+        var nodeID = this.previousSibling.previousSibling.getAttribute('data-history-node-id');
+        if (Drupal.history.needsServerCheck(nodeID, lastCommentTimestamp)) {
+          if (nodeIDs.indexOf(nodeID) === -1) {
+            nodeIDs.push(nodeID);
           }
-        });
+          return true;
+        } else {
+          return false;
+        }
+      });
 
       if ($nodeNewPlaceholders.length === 0 && $newRepliesPlaceholders.length === 0) {
         return;
       }
 
-      // Fetch the node read timestamps from the server.
       Drupal.history.fetchTimestamps(nodeIDs, function () {
         processNodeNewIndicators($nodeNewPlaceholders);
         processNewRepliesIndicators($newRepliesPlaceholders);
@@ -76,28 +63,24 @@
       var lastViewTimestamp = Drupal.history.getLastRead(nodeID);
 
       if (timestamp > lastViewTimestamp) {
-        var message = (lastViewTimestamp === 0) ? newNodeString : updatedNodeString;
+        var message = lastViewTimestamp === 0 ? newNodeString : updatedNodeString;
         $(placeholder).append('<span class="marker">' + message + '</span>');
       }
     });
   }
 
   function processNewRepliesIndicators($placeholders) {
-    // Figure out which placeholders need the "x new" replies links.
     var placeholdersToUpdate = {};
     $placeholders.each(function (index, placeholder) {
       var timestamp = parseInt(placeholder.getAttribute('data-history-node-last-comment-timestamp'), 10);
       var nodeID = placeholder.previousSibling.previousSibling.getAttribute('data-history-node-id');
       var lastViewTimestamp = Drupal.history.getLastRead(nodeID);
 
-      // Queue this placeholder's "X new" replies link to be downloaded from the
-      // server.
       if (timestamp > lastViewTimestamp) {
         placeholdersToUpdate[nodeID] = placeholder;
       }
     });
 
-    // Perform an AJAX request to retrieve node view timestamps.
     var nodeIDs = Object.keys(placeholdersToUpdate);
     if (nodeIDs.length === 0) {
       return;
@@ -105,9 +88,9 @@
     $.ajax({
       url: Drupal.url('comments/render_new_comments_node_links'),
       type: 'POST',
-      data: {'node_ids[]': nodeIDs},
+      data: { 'node_ids[]': nodeIDs },
       dataType: 'json',
-      success: function (results) {
+      success: function success(results) {
         for (var nodeID in results) {
           if (results.hasOwnProperty(nodeID) && placeholdersToUpdate.hasOwnProperty(nodeID)) {
             var url = results[nodeID].first_new_comment_link;
@@ -118,5 +101,4 @@
       }
     });
   }
-
-})(jQuery, Drupal, window);
+})(jQuery, Drupal, window);
\ No newline at end of file
diff --git a/core/modules/user/user.es6.js b/core/modules/user/user.es6.js
new file mode 100644
index 000000000000..f4602c676038
--- /dev/null
+++ b/core/modules/user/user.es6.js
@@ -0,0 +1,217 @@
+/**
+ * @file
+ * User behaviors.
+ */
+
+(function ($, Drupal, drupalSettings) {
+
+  'use strict';
+
+  /**
+   * Attach handlers to evaluate the strength of any password fields and to
+   * check that its confirmation is correct.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches password strength indicator and other relevant validation to
+   *   password fields.
+   */
+  Drupal.behaviors.password = {
+    attach: function (context, settings) {
+      var $passwordInput = $(context).find('input.js-password-field').once('password');
+
+      if ($passwordInput.length) {
+        var translate = settings.password;
+
+        var $passwordInputParent = $passwordInput.parent();
+        var $passwordInputParentWrapper = $passwordInputParent.parent();
+        var $passwordSuggestions;
+
+        // Add identifying class to password element parent.
+        $passwordInputParent.addClass('password-parent');
+
+        // Add the password confirmation layer.
+        $passwordInputParentWrapper
+          .find('input.js-password-confirm')
+          .parent()
+          .append('<div aria-live="polite" aria-atomic="true" class="password-confirm js-password-confirm">' + translate.confirmTitle + ' <span></span></div>')
+          .addClass('confirm-parent');
+
+        var $confirmInput = $passwordInputParentWrapper.find('input.js-password-confirm');
+        var $confirmResult = $passwordInputParentWrapper.find('div.js-password-confirm');
+        var $confirmChild = $confirmResult.find('span');
+
+        // If the password strength indicator is enabled, add its markup.
+        if (settings.password.showStrengthIndicator) {
+          var passwordMeter = '<div class="password-strength"><div class="password-strength__meter"><div class="password-strength__indicator js-password-strength__indicator"></div></div><div aria-live="polite" aria-atomic="true" class="password-strength__title">' + translate.strengthTitle + ' <span class="password-strength__text js-password-strength__text"></span></div></div>';
+          $confirmInput.parent().after('<div class="password-suggestions description"></div>');
+          $passwordInputParent.append(passwordMeter);
+          $passwordSuggestions = $passwordInputParentWrapper.find('div.password-suggestions').hide();
+        }
+
+        // Check that password and confirmation inputs match.
+        var passwordCheckMatch = function (confirmInputVal) {
+          var success = $passwordInput.val() === confirmInputVal;
+          var confirmClass = success ? 'ok' : 'error';
+
+          // Fill in the success message and set the class accordingly.
+          $confirmChild.html(translate['confirm' + (success ? 'Success' : 'Failure')])
+            .removeClass('ok error').addClass(confirmClass);
+        };
+
+        // Check the password strength.
+        var passwordCheck = function () {
+          if (settings.password.showStrengthIndicator) {
+            // Evaluate the password strength.
+            var result = Drupal.evaluatePasswordStrength($passwordInput.val(), settings.password);
+
+            // Update the suggestions for how to improve the password.
+            if ($passwordSuggestions.html() !== result.message) {
+              $passwordSuggestions.html(result.message);
+            }
+
+            // Only show the description box if a weakness exists in the
+            // password.
+            $passwordSuggestions.toggle(result.strength !== 100);
+
+            // Adjust the length of the strength indicator.
+            $passwordInputParent.find('.js-password-strength__indicator')
+              .css('width', result.strength + '%')
+              .removeClass('is-weak is-fair is-good is-strong')
+              .addClass(result.indicatorClass);
+
+            // Update the strength indication text.
+            $passwordInputParent.find('.js-password-strength__text').html(result.indicatorText);
+          }
+
+          // Check the value in the confirm input and show results.
+          if ($confirmInput.val()) {
+            passwordCheckMatch($confirmInput.val());
+            $confirmResult.css({visibility: 'visible'});
+          }
+          else {
+            $confirmResult.css({visibility: 'hidden'});
+          }
+        };
+
+        // Monitor input events.
+        $passwordInput.on('input', passwordCheck);
+        $confirmInput.on('input', passwordCheck);
+      }
+    }
+  };
+
+  /**
+   * Evaluate the strength of a user's password.
+   *
+   * Returns the estimated strength and the relevant output message.
+   *
+   * @param {string} password
+   *   The password to evaluate.
+   * @param {object} translate
+   *   An object containing the text to display for each strength level.
+   *
+   * @return {object}
+   *   An object containing strength, message, indicatorText and indicatorClass.
+   */
+  Drupal.evaluatePasswordStrength = function (password, translate) {
+    password = password.trim();
+    var indicatorText;
+    var indicatorClass;
+    var weaknesses = 0;
+    var strength = 100;
+    var msg = [];
+
+    var hasLowercase = /[a-z]/.test(password);
+    var hasUppercase = /[A-Z]/.test(password);
+    var hasNumbers = /[0-9]/.test(password);
+    var hasPunctuation = /[^a-zA-Z0-9]/.test(password);
+
+    // If there is a username edit box on the page, compare password to that,
+    // otherwise use value from the database.
+    var $usernameBox = $('input.username');
+    var username = ($usernameBox.length > 0) ? $usernameBox.val() : translate.username;
+
+    // Lose 5 points for every character less than 12, plus a 30 point penalty.
+    if (password.length < 12) {
+      msg.push(translate.tooShort);
+      strength -= ((12 - password.length) * 5) + 30;
+    }
+
+    // Count weaknesses.
+    if (!hasLowercase) {
+      msg.push(translate.addLowerCase);
+      weaknesses++;
+    }
+    if (!hasUppercase) {
+      msg.push(translate.addUpperCase);
+      weaknesses++;
+    }
+    if (!hasNumbers) {
+      msg.push(translate.addNumbers);
+      weaknesses++;
+    }
+    if (!hasPunctuation) {
+      msg.push(translate.addPunctuation);
+      weaknesses++;
+    }
+
+    // Apply penalty for each weakness (balanced against length penalty).
+    switch (weaknesses) {
+      case 1:
+        strength -= 12.5;
+        break;
+
+      case 2:
+        strength -= 25;
+        break;
+
+      case 3:
+        strength -= 40;
+        break;
+
+      case 4:
+        strength -= 40;
+        break;
+    }
+
+    // Check if password is the same as the username.
+    if (password !== '' && password.toLowerCase() === username.toLowerCase()) {
+      msg.push(translate.sameAsUsername);
+      // Passwords the same as username are always very weak.
+      strength = 5;
+    }
+
+    // Based on the strength, work out what text should be shown by the
+    // password strength meter.
+    if (strength < 60) {
+      indicatorText = translate.weak;
+      indicatorClass = 'is-weak';
+    }
+    else if (strength < 70) {
+      indicatorText = translate.fair;
+      indicatorClass = 'is-fair';
+    }
+    else if (strength < 80) {
+      indicatorText = translate.good;
+      indicatorClass = 'is-good';
+    }
+    else if (strength <= 100) {
+      indicatorText = translate.strong;
+      indicatorClass = 'is-strong';
+    }
+
+    // Assemble the final message.
+    msg = translate.hasWeaknesses + '<ul><li>' + msg.join('</li><li>') + '</li></ul>';
+
+    return {
+      strength: strength,
+      message: msg,
+      indicatorText: indicatorText,
+      indicatorClass: indicatorClass
+    };
+
+  };
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/user/user.js b/core/modules/user/user.js
index f4602c676038..ce7b85e501d8 100644
--- a/core/modules/user/user.js
+++ b/core/modules/user/user.js
@@ -1,24 +1,17 @@
 /**
- * @file
- * User behaviors.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/user/user.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings) {
 
   'use strict';
 
-  /**
-   * Attach handlers to evaluate the strength of any password fields and to
-   * check that its confirmation is correct.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches password strength indicator and other relevant validation to
-   *   password fields.
-   */
   Drupal.behaviors.password = {
-    attach: function (context, settings) {
+    attach: function attach(context, settings) {
       var $passwordInput = $(context).find('input.js-password-field').once('password');
 
       if ($passwordInput.length) {
@@ -28,21 +21,14 @@
         var $passwordInputParentWrapper = $passwordInputParent.parent();
         var $passwordSuggestions;
 
-        // Add identifying class to password element parent.
         $passwordInputParent.addClass('password-parent');
 
-        // Add the password confirmation layer.
-        $passwordInputParentWrapper
-          .find('input.js-password-confirm')
-          .parent()
-          .append('<div aria-live="polite" aria-atomic="true" class="password-confirm js-password-confirm">' + translate.confirmTitle + ' <span></span></div>')
-          .addClass('confirm-parent');
+        $passwordInputParentWrapper.find('input.js-password-confirm').parent().append('<div aria-live="polite" aria-atomic="true" class="password-confirm js-password-confirm">' + translate.confirmTitle + ' <span></span></div>').addClass('confirm-parent');
 
         var $confirmInput = $passwordInputParentWrapper.find('input.js-password-confirm');
         var $confirmResult = $passwordInputParentWrapper.find('div.js-password-confirm');
         var $confirmChild = $confirmResult.find('span');
 
-        // If the password strength indicator is enabled, add its markup.
         if (settings.password.showStrengthIndicator) {
           var passwordMeter = '<div class="password-strength"><div class="password-strength__meter"><div class="password-strength__indicator js-password-strength__indicator"></div></div><div aria-live="polite" aria-atomic="true" class="password-strength__title">' + translate.strengthTitle + ' <span class="password-strength__text js-password-strength__text"></span></div></div>';
           $confirmInput.parent().after('<div class="password-suggestions description"></div>');
@@ -50,71 +36,42 @@
           $passwordSuggestions = $passwordInputParentWrapper.find('div.password-suggestions').hide();
         }
 
-        // Check that password and confirmation inputs match.
-        var passwordCheckMatch = function (confirmInputVal) {
+        var passwordCheckMatch = function passwordCheckMatch(confirmInputVal) {
           var success = $passwordInput.val() === confirmInputVal;
           var confirmClass = success ? 'ok' : 'error';
 
-          // Fill in the success message and set the class accordingly.
-          $confirmChild.html(translate['confirm' + (success ? 'Success' : 'Failure')])
-            .removeClass('ok error').addClass(confirmClass);
+          $confirmChild.html(translate['confirm' + (success ? 'Success' : 'Failure')]).removeClass('ok error').addClass(confirmClass);
         };
 
-        // Check the password strength.
-        var passwordCheck = function () {
+        var passwordCheck = function passwordCheck() {
           if (settings.password.showStrengthIndicator) {
-            // Evaluate the password strength.
             var result = Drupal.evaluatePasswordStrength($passwordInput.val(), settings.password);
 
-            // Update the suggestions for how to improve the password.
             if ($passwordSuggestions.html() !== result.message) {
               $passwordSuggestions.html(result.message);
             }
 
-            // Only show the description box if a weakness exists in the
-            // password.
             $passwordSuggestions.toggle(result.strength !== 100);
 
-            // Adjust the length of the strength indicator.
-            $passwordInputParent.find('.js-password-strength__indicator')
-              .css('width', result.strength + '%')
-              .removeClass('is-weak is-fair is-good is-strong')
-              .addClass(result.indicatorClass);
+            $passwordInputParent.find('.js-password-strength__indicator').css('width', result.strength + '%').removeClass('is-weak is-fair is-good is-strong').addClass(result.indicatorClass);
 
-            // Update the strength indication text.
             $passwordInputParent.find('.js-password-strength__text').html(result.indicatorText);
           }
 
-          // Check the value in the confirm input and show results.
           if ($confirmInput.val()) {
             passwordCheckMatch($confirmInput.val());
-            $confirmResult.css({visibility: 'visible'});
-          }
-          else {
-            $confirmResult.css({visibility: 'hidden'});
+            $confirmResult.css({ visibility: 'visible' });
+          } else {
+            $confirmResult.css({ visibility: 'hidden' });
           }
         };
 
-        // Monitor input events.
         $passwordInput.on('input', passwordCheck);
         $confirmInput.on('input', passwordCheck);
       }
     }
   };
 
-  /**
-   * Evaluate the strength of a user's password.
-   *
-   * Returns the estimated strength and the relevant output message.
-   *
-   * @param {string} password
-   *   The password to evaluate.
-   * @param {object} translate
-   *   An object containing the text to display for each strength level.
-   *
-   * @return {object}
-   *   An object containing strength, message, indicatorText and indicatorClass.
-   */
   Drupal.evaluatePasswordStrength = function (password, translate) {
     password = password.trim();
     var indicatorText;
@@ -128,18 +85,14 @@
     var hasNumbers = /[0-9]/.test(password);
     var hasPunctuation = /[^a-zA-Z0-9]/.test(password);
 
-    // If there is a username edit box on the page, compare password to that,
-    // otherwise use value from the database.
     var $usernameBox = $('input.username');
-    var username = ($usernameBox.length > 0) ? $usernameBox.val() : translate.username;
+    var username = $usernameBox.length > 0 ? $usernameBox.val() : translate.username;
 
-    // Lose 5 points for every character less than 12, plus a 30 point penalty.
     if (password.length < 12) {
       msg.push(translate.tooShort);
-      strength -= ((12 - password.length) * 5) + 30;
+      strength -= (12 - password.length) * 5 + 30;
     }
 
-    // Count weaknesses.
     if (!hasLowercase) {
       msg.push(translate.addLowerCase);
       weaknesses++;
@@ -157,7 +110,6 @@
       weaknesses++;
     }
 
-    // Apply penalty for each weakness (balanced against length penalty).
     switch (weaknesses) {
       case 1:
         strength -= 12.5;
@@ -176,33 +128,26 @@
         break;
     }
 
-    // Check if password is the same as the username.
     if (password !== '' && password.toLowerCase() === username.toLowerCase()) {
       msg.push(translate.sameAsUsername);
-      // Passwords the same as username are always very weak.
+
       strength = 5;
     }
 
-    // Based on the strength, work out what text should be shown by the
-    // password strength meter.
     if (strength < 60) {
       indicatorText = translate.weak;
       indicatorClass = 'is-weak';
-    }
-    else if (strength < 70) {
+    } else if (strength < 70) {
       indicatorText = translate.fair;
       indicatorClass = 'is-fair';
-    }
-    else if (strength < 80) {
+    } else if (strength < 80) {
       indicatorText = translate.good;
       indicatorClass = 'is-good';
-    }
-    else if (strength <= 100) {
+    } else if (strength <= 100) {
       indicatorText = translate.strong;
       indicatorClass = 'is-strong';
     }
 
-    // Assemble the final message.
     msg = translate.hasWeaknesses + '<ul><li>' + msg.join('</li><li>') + '</li></ul>';
 
     return {
@@ -211,7 +156,5 @@
       indicatorText: indicatorText,
       indicatorClass: indicatorClass
     };
-
   };
-
-})(jQuery, Drupal, drupalSettings);
+})(jQuery, Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/modules/user/user.permissions.es6.js b/core/modules/user/user.permissions.es6.js
new file mode 100644
index 000000000000..c3dc24f1f0a6
--- /dev/null
+++ b/core/modules/user/user.permissions.es6.js
@@ -0,0 +1,88 @@
+/**
+ * @file
+ * User permission page behaviors.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * Shows checked and disabled checkboxes for inherited permissions.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches functionality to the permissions table.
+   */
+  Drupal.behaviors.permissions = {
+    attach: function (context) {
+      var self = this;
+      $('table#permissions').once('permissions').each(function () {
+        // On a site with many roles and permissions, this behavior initially
+        // has to perform thousands of DOM manipulations to inject checkboxes
+        // and hide them. By detaching the table from the DOM, all operations
+        // can be performed without triggering internal layout and re-rendering
+        // processes in the browser.
+        var $table = $(this);
+        var $ancestor;
+        var method;
+        if ($table.prev().length) {
+          $ancestor = $table.prev();
+          method = 'after';
+        }
+        else {
+          $ancestor = $table.parent();
+          method = 'append';
+        }
+        $table.detach();
+
+        // Create dummy checkboxes. We use dummy checkboxes instead of reusing
+        // the existing checkboxes here because new checkboxes don't alter the
+        // submitted form. If we'd automatically check existing checkboxes, the
+        // permission table would be polluted with redundant entries. This
+        // is deliberate, but desirable when we automatically check them.
+        var $dummy = $('<input type="checkbox" class="dummy-checkbox js-dummy-checkbox" disabled="disabled" checked="checked" />')
+          .attr('title', Drupal.t('This permission is inherited from the authenticated user role.'))
+          .hide();
+
+        $table
+          .find('input[type="checkbox"]')
+          .not('.js-rid-anonymous, .js-rid-authenticated')
+          .addClass('real-checkbox js-real-checkbox')
+          .after($dummy);
+
+        // Initialize the authenticated user checkbox.
+        $table.find('input[type=checkbox].js-rid-authenticated')
+          .on('click.permissions', self.toggle)
+          // .triggerHandler() cannot be used here, as it only affects the first
+          // element.
+          .each(self.toggle);
+
+        // Re-insert the table into the DOM.
+        $ancestor[method]($table);
+      });
+    },
+
+    /**
+     * Toggles all dummy checkboxes based on the checkboxes' state.
+     *
+     * If the "authenticated user" checkbox is checked, the checked and disabled
+     * checkboxes are shown, the real checkboxes otherwise.
+     */
+    toggle: function () {
+      var authCheckbox = this;
+      var $row = $(this).closest('tr');
+      // jQuery performs too many layout calculations for .hide() and .show(),
+      // leading to a major page rendering lag on sites with many roles and
+      // permissions. Therefore, we toggle visibility directly.
+      $row.find('.js-real-checkbox').each(function () {
+        this.style.display = (authCheckbox.checked ? 'none' : '');
+      });
+      $row.find('.js-dummy-checkbox').each(function () {
+        this.style.display = (authCheckbox.checked ? '' : 'none');
+      });
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/core/modules/user/user.permissions.js b/core/modules/user/user.permissions.js
index c3dc24f1f0a6..f7c8b82e21a7 100644
--- a/core/modules/user/user.permissions.js
+++ b/core/modules/user/user.permissions.js
@@ -1,88 +1,51 @@
 /**
- * @file
- * User permission page behaviors.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/user/user.permissions.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   * Shows checked and disabled checkboxes for inherited permissions.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches functionality to the permissions table.
-   */
   Drupal.behaviors.permissions = {
-    attach: function (context) {
+    attach: function attach(context) {
       var self = this;
       $('table#permissions').once('permissions').each(function () {
-        // On a site with many roles and permissions, this behavior initially
-        // has to perform thousands of DOM manipulations to inject checkboxes
-        // and hide them. By detaching the table from the DOM, all operations
-        // can be performed without triggering internal layout and re-rendering
-        // processes in the browser.
         var $table = $(this);
         var $ancestor;
         var method;
         if ($table.prev().length) {
           $ancestor = $table.prev();
           method = 'after';
-        }
-        else {
+        } else {
           $ancestor = $table.parent();
           method = 'append';
         }
         $table.detach();
 
-        // Create dummy checkboxes. We use dummy checkboxes instead of reusing
-        // the existing checkboxes here because new checkboxes don't alter the
-        // submitted form. If we'd automatically check existing checkboxes, the
-        // permission table would be polluted with redundant entries. This
-        // is deliberate, but desirable when we automatically check them.
-        var $dummy = $('<input type="checkbox" class="dummy-checkbox js-dummy-checkbox" disabled="disabled" checked="checked" />')
-          .attr('title', Drupal.t('This permission is inherited from the authenticated user role.'))
-          .hide();
+        var $dummy = $('<input type="checkbox" class="dummy-checkbox js-dummy-checkbox" disabled="disabled" checked="checked" />').attr('title', Drupal.t('This permission is inherited from the authenticated user role.')).hide();
 
-        $table
-          .find('input[type="checkbox"]')
-          .not('.js-rid-anonymous, .js-rid-authenticated')
-          .addClass('real-checkbox js-real-checkbox')
-          .after($dummy);
+        $table.find('input[type="checkbox"]').not('.js-rid-anonymous, .js-rid-authenticated').addClass('real-checkbox js-real-checkbox').after($dummy);
 
-        // Initialize the authenticated user checkbox.
-        $table.find('input[type=checkbox].js-rid-authenticated')
-          .on('click.permissions', self.toggle)
-          // .triggerHandler() cannot be used here, as it only affects the first
-          // element.
-          .each(self.toggle);
+        $table.find('input[type=checkbox].js-rid-authenticated').on('click.permissions', self.toggle).each(self.toggle);
 
-        // Re-insert the table into the DOM.
         $ancestor[method]($table);
       });
     },
 
-    /**
-     * Toggles all dummy checkboxes based on the checkboxes' state.
-     *
-     * If the "authenticated user" checkbox is checked, the checked and disabled
-     * checkboxes are shown, the real checkboxes otherwise.
-     */
-    toggle: function () {
+    toggle: function toggle() {
       var authCheckbox = this;
       var $row = $(this).closest('tr');
-      // jQuery performs too many layout calculations for .hide() and .show(),
-      // leading to a major page rendering lag on sites with many roles and
-      // permissions. Therefore, we toggle visibility directly.
+
       $row.find('.js-real-checkbox').each(function () {
-        this.style.display = (authCheckbox.checked ? 'none' : '');
+        this.style.display = authCheckbox.checked ? 'none' : '';
       });
       $row.find('.js-dummy-checkbox').each(function () {
-        this.style.display = (authCheckbox.checked ? '' : 'none');
+        this.style.display = authCheckbox.checked ? '' : 'none';
       });
     }
   };
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/modules/views/js/ajax_view.es6.js b/core/modules/views/js/ajax_view.es6.js
new file mode 100644
index 000000000000..85975b56ce35
--- /dev/null
+++ b/core/modules/views/js/ajax_view.es6.js
@@ -0,0 +1,205 @@
+/**
+ * @file
+ * Handles AJAX fetching of views, including filter submission and response.
+ */
+
+(function ($, Drupal, drupalSettings) {
+
+  'use strict';
+
+  /**
+   * Attaches the AJAX behavior to exposed filters forms and key View links.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches ajaxView functionality to relevant elements.
+   */
+  Drupal.behaviors.ViewsAjaxView = {};
+  Drupal.behaviors.ViewsAjaxView.attach = function () {
+    if (drupalSettings && drupalSettings.views && drupalSettings.views.ajaxViews) {
+      var ajaxViews = drupalSettings.views.ajaxViews;
+      for (var i in ajaxViews) {
+        if (ajaxViews.hasOwnProperty(i)) {
+          Drupal.views.instances[i] = new Drupal.views.ajaxView(ajaxViews[i]);
+        }
+      }
+    }
+  };
+
+  /**
+   * @namespace
+   */
+  Drupal.views = {};
+
+  /**
+   * @type {object.<string, Drupal.views.ajaxView>}
+   */
+  Drupal.views.instances = {};
+
+  /**
+   * Javascript object for a certain view.
+   *
+   * @constructor
+   *
+   * @param {object} settings
+   *   Settings object for the ajax view.
+   * @param {string} settings.view_dom_id
+   *   The DOM id of the view.
+   */
+  Drupal.views.ajaxView = function (settings) {
+    var selector = '.js-view-dom-id-' + settings.view_dom_id;
+    this.$view = $(selector);
+
+    // Retrieve the path to use for views' ajax.
+    var ajax_path = drupalSettings.views.ajax_path;
+
+    // If there are multiple views this might've ended up showing up multiple
+    // times.
+    if (ajax_path.constructor.toString().indexOf('Array') !== -1) {
+      ajax_path = ajax_path[0];
+    }
+
+    // Check if there are any GET parameters to send to views.
+    var queryString = window.location.search || '';
+    if (queryString !== '') {
+      // Remove the question mark and Drupal path component if any.
+      queryString = queryString.slice(1).replace(/q=[^&]+&?|&?render=[^&]+/, '');
+      if (queryString !== '') {
+        // If there is a '?' in ajax_path, clean url are on and & should be
+        // used to add parameters.
+        queryString = ((/\?/.test(ajax_path)) ? '&' : '?') + queryString;
+      }
+    }
+
+    this.element_settings = {
+      url: ajax_path + queryString,
+      submit: settings,
+      setClick: true,
+      event: 'click',
+      selector: selector,
+      progress: {type: 'fullscreen'}
+    };
+
+    this.settings = settings;
+
+    // Add the ajax to exposed forms.
+    this.$exposed_form = $('form#views-exposed-form-' + settings.view_name.replace(/_/g, '-') + '-' + settings.view_display_id.replace(/_/g, '-'));
+    this.$exposed_form.once('exposed-form').each($.proxy(this.attachExposedFormAjax, this));
+
+    // Add the ajax to pagers.
+    this.$view
+      // Don't attach to nested views. Doing so would attach multiple behaviors
+      // to a given element.
+      .filter($.proxy(this.filterNestedViews, this))
+      .once('ajax-pager').each($.proxy(this.attachPagerAjax, this));
+
+    // Add a trigger to update this view specifically. In order to trigger a
+    // refresh use the following code.
+    //
+    // @code
+    // $('.view-name').trigger('RefreshView');
+    // @endcode
+    var self_settings = $.extend({}, this.element_settings, {
+      event: 'RefreshView',
+      base: this.selector,
+      element: this.$view.get(0)
+    });
+    this.refreshViewAjax = Drupal.ajax(self_settings);
+  };
+
+  /**
+   * @method
+   */
+  Drupal.views.ajaxView.prototype.attachExposedFormAjax = function () {
+    var that = this;
+    this.exposedFormAjax = [];
+    // Exclude the reset buttons so no AJAX behaviours are bound. Many things
+    // break during the form reset phase if using AJAX.
+    $('input[type=submit], input[type=image]', this.$exposed_form).not('[data-drupal-selector=edit-reset]').each(function (index) {
+      var self_settings = $.extend({}, that.element_settings, {
+        base: $(this).attr('id'),
+        element: this
+      });
+      that.exposedFormAjax[index] = Drupal.ajax(self_settings);
+    });
+  };
+
+  /**
+   * @return {bool}
+   *   If there is at least one parent with a view class return false.
+   */
+  Drupal.views.ajaxView.prototype.filterNestedViews = function () {
+    // If there is at least one parent with a view class, this view
+    // is nested (e.g., an attachment). Bail.
+    return !this.$view.parents('.view').length;
+  };
+
+  /**
+   * Attach the ajax behavior to each link.
+   */
+  Drupal.views.ajaxView.prototype.attachPagerAjax = function () {
+    this.$view.find('ul.js-pager__items > li > a, th.views-field a, .attachment .views-summary a')
+      .each($.proxy(this.attachPagerLinkAjax, this));
+  };
+
+  /**
+   * Attach the ajax behavior to a singe link.
+   *
+   * @param {string} [id]
+   *   The ID of the link.
+   * @param {HTMLElement} link
+   *   The link element.
+   */
+  Drupal.views.ajaxView.prototype.attachPagerLinkAjax = function (id, link) {
+    var $link = $(link);
+    var viewData = {};
+    var href = $link.attr('href');
+    // Construct an object using the settings defaults and then overriding
+    // with data specific to the link.
+    $.extend(
+      viewData,
+      this.settings,
+      Drupal.Views.parseQueryString(href),
+      // Extract argument data from the URL.
+      Drupal.Views.parseViewArgs(href, this.settings.view_base_path)
+    );
+
+    var self_settings = $.extend({}, this.element_settings, {
+      submit: viewData,
+      base: false,
+      element: link
+    });
+    this.pagerAjax = Drupal.ajax(self_settings);
+  };
+
+  /**
+   * Views scroll to top ajax command.
+   *
+   * @param {Drupal.Ajax} [ajax]
+   *   A {@link Drupal.ajax} object.
+   * @param {object} response
+   *   Ajax response.
+   * @param {string} response.selector
+   *   Selector to use.
+   */
+  Drupal.AjaxCommands.prototype.viewsScrollTop = function (ajax, response) {
+    // Scroll to the top of the view. This will allow users
+    // to browse newly loaded content after e.g. clicking a pager
+    // link.
+    var offset = $(response.selector).offset();
+    // We can't guarantee that the scrollable object should be
+    // the body, as the view could be embedded in something
+    // more complex such as a modal popup. Recurse up the DOM
+    // and scroll the first element that has a non-zero top.
+    var scrollTarget = response.selector;
+    while ($(scrollTarget).scrollTop() === 0 && $(scrollTarget).parent()) {
+      scrollTarget = $(scrollTarget).parent();
+    }
+    // Only scroll upward.
+    if (offset.top - 10 < $(scrollTarget).scrollTop()) {
+      $(scrollTarget).animate({scrollTop: (offset.top - 10)}, 500);
+    }
+  };
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/views/js/ajax_view.js b/core/modules/views/js/ajax_view.js
index 85975b56ce35..876cdf11e140 100644
--- a/core/modules/views/js/ajax_view.js
+++ b/core/modules/views/js/ajax_view.js
@@ -1,20 +1,15 @@
 /**
- * @file
- * Handles AJAX fetching of views, including filter submission and response.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/views/js/ajax_view.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings) {
 
   'use strict';
 
-  /**
-   * Attaches the AJAX behavior to exposed filters forms and key View links.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches ajaxView functionality to relevant elements.
-   */
   Drupal.behaviors.ViewsAjaxView = {};
   Drupal.behaviors.ViewsAjaxView.attach = function () {
     if (drupalSettings && drupalSettings.views && drupalSettings.views.ajaxViews) {
@@ -27,48 +22,25 @@
     }
   };
 
-  /**
-   * @namespace
-   */
   Drupal.views = {};
 
-  /**
-   * @type {object.<string, Drupal.views.ajaxView>}
-   */
   Drupal.views.instances = {};
 
-  /**
-   * Javascript object for a certain view.
-   *
-   * @constructor
-   *
-   * @param {object} settings
-   *   Settings object for the ajax view.
-   * @param {string} settings.view_dom_id
-   *   The DOM id of the view.
-   */
   Drupal.views.ajaxView = function (settings) {
     var selector = '.js-view-dom-id-' + settings.view_dom_id;
     this.$view = $(selector);
 
-    // Retrieve the path to use for views' ajax.
     var ajax_path = drupalSettings.views.ajax_path;
 
-    // If there are multiple views this might've ended up showing up multiple
-    // times.
     if (ajax_path.constructor.toString().indexOf('Array') !== -1) {
       ajax_path = ajax_path[0];
     }
 
-    // Check if there are any GET parameters to send to views.
     var queryString = window.location.search || '';
     if (queryString !== '') {
-      // Remove the question mark and Drupal path component if any.
       queryString = queryString.slice(1).replace(/q=[^&]+&?|&?render=[^&]+/, '');
       if (queryString !== '') {
-        // If there is a '?' in ajax_path, clean url are on and & should be
-        // used to add parameters.
-        queryString = ((/\?/.test(ajax_path)) ? '&' : '?') + queryString;
+        queryString = (/\?/.test(ajax_path) ? '&' : '?') + queryString;
       }
     }
 
@@ -78,28 +50,16 @@
       setClick: true,
       event: 'click',
       selector: selector,
-      progress: {type: 'fullscreen'}
+      progress: { type: 'fullscreen' }
     };
 
     this.settings = settings;
 
-    // Add the ajax to exposed forms.
     this.$exposed_form = $('form#views-exposed-form-' + settings.view_name.replace(/_/g, '-') + '-' + settings.view_display_id.replace(/_/g, '-'));
     this.$exposed_form.once('exposed-form').each($.proxy(this.attachExposedFormAjax, this));
 
-    // Add the ajax to pagers.
-    this.$view
-      // Don't attach to nested views. Doing so would attach multiple behaviors
-      // to a given element.
-      .filter($.proxy(this.filterNestedViews, this))
-      .once('ajax-pager').each($.proxy(this.attachPagerAjax, this));
-
-    // Add a trigger to update this view specifically. In order to trigger a
-    // refresh use the following code.
-    //
-    // @code
-    // $('.view-name').trigger('RefreshView');
-    // @endcode
+    this.$view.filter($.proxy(this.filterNestedViews, this)).once('ajax-pager').each($.proxy(this.attachPagerAjax, this));
+
     var self_settings = $.extend({}, this.element_settings, {
       event: 'RefreshView',
       base: this.selector,
@@ -108,14 +68,10 @@
     this.refreshViewAjax = Drupal.ajax(self_settings);
   };
 
-  /**
-   * @method
-   */
   Drupal.views.ajaxView.prototype.attachExposedFormAjax = function () {
     var that = this;
     this.exposedFormAjax = [];
-    // Exclude the reset buttons so no AJAX behaviours are bound. Many things
-    // break during the form reset phase if using AJAX.
+
     $('input[type=submit], input[type=image]', this.$exposed_form).not('[data-drupal-selector=edit-reset]').each(function (index) {
       var self_settings = $.extend({}, that.element_settings, {
         base: $(this).attr('id'),
@@ -125,45 +81,20 @@
     });
   };
 
-  /**
-   * @return {bool}
-   *   If there is at least one parent with a view class return false.
-   */
   Drupal.views.ajaxView.prototype.filterNestedViews = function () {
-    // If there is at least one parent with a view class, this view
-    // is nested (e.g., an attachment). Bail.
     return !this.$view.parents('.view').length;
   };
 
-  /**
-   * Attach the ajax behavior to each link.
-   */
   Drupal.views.ajaxView.prototype.attachPagerAjax = function () {
-    this.$view.find('ul.js-pager__items > li > a, th.views-field a, .attachment .views-summary a')
-      .each($.proxy(this.attachPagerLinkAjax, this));
+    this.$view.find('ul.js-pager__items > li > a, th.views-field a, .attachment .views-summary a').each($.proxy(this.attachPagerLinkAjax, this));
   };
 
-  /**
-   * Attach the ajax behavior to a singe link.
-   *
-   * @param {string} [id]
-   *   The ID of the link.
-   * @param {HTMLElement} link
-   *   The link element.
-   */
   Drupal.views.ajaxView.prototype.attachPagerLinkAjax = function (id, link) {
     var $link = $(link);
     var viewData = {};
     var href = $link.attr('href');
-    // Construct an object using the settings defaults and then overriding
-    // with data specific to the link.
-    $.extend(
-      viewData,
-      this.settings,
-      Drupal.Views.parseQueryString(href),
-      // Extract argument data from the URL.
-      Drupal.Views.parseViewArgs(href, this.settings.view_base_path)
-    );
+
+    $.extend(viewData, this.settings, Drupal.Views.parseQueryString(href), Drupal.Views.parseViewArgs(href, this.settings.view_base_path));
 
     var self_settings = $.extend({}, this.element_settings, {
       submit: viewData,
@@ -173,33 +104,16 @@
     this.pagerAjax = Drupal.ajax(self_settings);
   };
 
-  /**
-   * Views scroll to top ajax command.
-   *
-   * @param {Drupal.Ajax} [ajax]
-   *   A {@link Drupal.ajax} object.
-   * @param {object} response
-   *   Ajax response.
-   * @param {string} response.selector
-   *   Selector to use.
-   */
   Drupal.AjaxCommands.prototype.viewsScrollTop = function (ajax, response) {
-    // Scroll to the top of the view. This will allow users
-    // to browse newly loaded content after e.g. clicking a pager
-    // link.
     var offset = $(response.selector).offset();
-    // We can't guarantee that the scrollable object should be
-    // the body, as the view could be embedded in something
-    // more complex such as a modal popup. Recurse up the DOM
-    // and scroll the first element that has a non-zero top.
+
     var scrollTarget = response.selector;
     while ($(scrollTarget).scrollTop() === 0 && $(scrollTarget).parent()) {
       scrollTarget = $(scrollTarget).parent();
     }
-    // Only scroll upward.
+
     if (offset.top - 10 < $(scrollTarget).scrollTop()) {
-      $(scrollTarget).animate({scrollTop: (offset.top - 10)}, 500);
+      $(scrollTarget).animate({ scrollTop: offset.top - 10 }, 500);
     }
   };
-
-})(jQuery, Drupal, drupalSettings);
+})(jQuery, Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/modules/views/js/base.es6.js b/core/modules/views/js/base.es6.js
new file mode 100644
index 000000000000..cca3be4ddecd
--- /dev/null
+++ b/core/modules/views/js/base.es6.js
@@ -0,0 +1,110 @@
+/**
+ * @file
+ * Some basic behaviors and utility functions for Views.
+ */
+
+(function ($, Drupal, drupalSettings) {
+
+  'use strict';
+
+  /**
+   * @namespace
+   */
+  Drupal.Views = {};
+
+  /**
+   * Helper function to parse a querystring.
+   *
+   * @param {string} query
+   *   The querystring to parse.
+   *
+   * @return {object}
+   *   A map of query parameters.
+   */
+  Drupal.Views.parseQueryString = function (query) {
+    var args = {};
+    var pos = query.indexOf('?');
+    if (pos !== -1) {
+      query = query.substring(pos + 1);
+    }
+    var pair;
+    var pairs = query.split('&');
+    for (var i = 0; i < pairs.length; i++) {
+      pair = pairs[i].split('=');
+      // Ignore the 'q' path argument, if present.
+      if (pair[0] !== 'q' && pair[1]) {
+        args[decodeURIComponent(pair[0].replace(/\+/g, ' '))] = decodeURIComponent(pair[1].replace(/\+/g, ' '));
+      }
+    }
+    return args;
+  };
+
+  /**
+   * Helper function to return a view's arguments based on a path.
+   *
+   * @param {string} href
+   *   The href to check.
+   * @param {string} viewPath
+   *   The views path to check.
+   *
+   * @return {object}
+   *   An object containing `view_args` and `view_path`.
+   */
+  Drupal.Views.parseViewArgs = function (href, viewPath) {
+    var returnObj = {};
+    var path = Drupal.Views.getPath(href);
+    // Get viewPath url without baseUrl portion.
+    var viewHref = Drupal.url(viewPath).substring(drupalSettings.path.baseUrl.length);
+    // Ensure we have a correct path.
+    if (viewHref && path.substring(0, viewHref.length + 1) === viewHref + '/') {
+      returnObj.view_args = decodeURIComponent(path.substring(viewHref.length + 1, path.length));
+      returnObj.view_path = path;
+    }
+    return returnObj;
+  };
+
+  /**
+   * Strip off the protocol plus domain from an href.
+   *
+   * @param {string} href
+   *   The href to strip.
+   *
+   * @return {string}
+   *   The href without the protocol and domain.
+   */
+  Drupal.Views.pathPortion = function (href) {
+    // Remove e.g. http://example.com if present.
+    var protocol = window.location.protocol;
+    if (href.substring(0, protocol.length) === protocol) {
+      // 2 is the length of the '//' that normally follows the protocol.
+      href = href.substring(href.indexOf('/', protocol.length + 2));
+    }
+    return href;
+  };
+
+  /**
+   * Return the Drupal path portion of an href.
+   *
+   * @param {string} href
+   *   The href to check.
+   *
+   * @return {string}
+   *   An internal path.
+   */
+  Drupal.Views.getPath = function (href) {
+    href = Drupal.Views.pathPortion(href);
+    href = href.substring(drupalSettings.path.baseUrl.length, href.length);
+    // 3 is the length of the '?q=' added to the url without clean urls.
+    if (href.substring(0, 3) === '?q=') {
+      href = href.substring(3, href.length);
+    }
+    var chars = ['#', '?', '&'];
+    for (var i = 0; i < chars.length; i++) {
+      if (href.indexOf(chars[i]) > -1) {
+        href = href.substr(0, href.indexOf(chars[i]));
+      }
+    }
+    return href;
+  };
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/views/js/base.js b/core/modules/views/js/base.js
index cca3be4ddecd..510254be3dda 100644
--- a/core/modules/views/js/base.js
+++ b/core/modules/views/js/base.js
@@ -1,26 +1,17 @@
 /**
- * @file
- * Some basic behaviors and utility functions for Views.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/views/js/base.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings) {
 
   'use strict';
 
-  /**
-   * @namespace
-   */
   Drupal.Views = {};
 
-  /**
-   * Helper function to parse a querystring.
-   *
-   * @param {string} query
-   *   The querystring to parse.
-   *
-   * @return {object}
-   *   A map of query parameters.
-   */
   Drupal.Views.parseQueryString = function (query) {
     var args = {};
     var pos = query.indexOf('?');
@@ -31,7 +22,7 @@
     var pairs = query.split('&');
     for (var i = 0; i < pairs.length; i++) {
       pair = pairs[i].split('=');
-      // Ignore the 'q' path argument, if present.
+
       if (pair[0] !== 'q' && pair[1]) {
         args[decodeURIComponent(pair[0].replace(/\+/g, ' '))] = decodeURIComponent(pair[1].replace(/\+/g, ' '));
       }
@@ -39,23 +30,12 @@
     return args;
   };
 
-  /**
-   * Helper function to return a view's arguments based on a path.
-   *
-   * @param {string} href
-   *   The href to check.
-   * @param {string} viewPath
-   *   The views path to check.
-   *
-   * @return {object}
-   *   An object containing `view_args` and `view_path`.
-   */
   Drupal.Views.parseViewArgs = function (href, viewPath) {
     var returnObj = {};
     var path = Drupal.Views.getPath(href);
-    // Get viewPath url without baseUrl portion.
+
     var viewHref = Drupal.url(viewPath).substring(drupalSettings.path.baseUrl.length);
-    // Ensure we have a correct path.
+
     if (viewHref && path.substring(0, viewHref.length + 1) === viewHref + '/') {
       returnObj.view_args = decodeURIComponent(path.substring(viewHref.length + 1, path.length));
       returnObj.view_path = path;
@@ -63,38 +43,18 @@
     return returnObj;
   };
 
-  /**
-   * Strip off the protocol plus domain from an href.
-   *
-   * @param {string} href
-   *   The href to strip.
-   *
-   * @return {string}
-   *   The href without the protocol and domain.
-   */
   Drupal.Views.pathPortion = function (href) {
-    // Remove e.g. http://example.com if present.
     var protocol = window.location.protocol;
     if (href.substring(0, protocol.length) === protocol) {
-      // 2 is the length of the '//' that normally follows the protocol.
       href = href.substring(href.indexOf('/', protocol.length + 2));
     }
     return href;
   };
 
-  /**
-   * Return the Drupal path portion of an href.
-   *
-   * @param {string} href
-   *   The href to check.
-   *
-   * @return {string}
-   *   An internal path.
-   */
   Drupal.Views.getPath = function (href) {
     href = Drupal.Views.pathPortion(href);
     href = href.substring(drupalSettings.path.baseUrl.length, href.length);
-    // 3 is the length of the '?q=' added to the url without clean urls.
+
     if (href.substring(0, 3) === '?q=') {
       href = href.substring(3, href.length);
     }
@@ -106,5 +66,4 @@
     }
     return href;
   };
-
-})(jQuery, Drupal, drupalSettings);
+})(jQuery, Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/modules/views/tests/modules/views_test_data/views_cache.test.es6.js b/core/modules/views/tests/modules/views_test_data/views_cache.test.es6.js
new file mode 100644
index 000000000000..4089267f9a5c
--- /dev/null
+++ b/core/modules/views/tests/modules/views_test_data/views_cache.test.es6.js
@@ -0,0 +1,8 @@
+/**
+ * @file
+ * Just a placeholder file for the test.
+ *
+ * @see ViewsCacheTest::testHeaderStorage
+ *
+ * @ignore
+ */
diff --git a/core/modules/views/tests/modules/views_test_data/views_cache.test.js b/core/modules/views/tests/modules/views_test_data/views_cache.test.js
index 4089267f9a5c..aa35fbaa96c2 100644
--- a/core/modules/views/tests/modules/views_test_data/views_cache.test.js
+++ b/core/modules/views/tests/modules/views_test_data/views_cache.test.js
@@ -1,8 +1,7 @@
 /**
- * @file
- * Just a placeholder file for the test.
- *
- * @see ViewsCacheTest::testHeaderStorage
- *
- * @ignore
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/views/tests/modules/views_test_data/views_cache.test.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
\ No newline at end of file
diff --git a/core/modules/views_ui/js/ajax.es6.js b/core/modules/views_ui/js/ajax.es6.js
new file mode 100644
index 000000000000..d4ff7642f8fb
--- /dev/null
+++ b/core/modules/views_ui/js/ajax.es6.js
@@ -0,0 +1,249 @@
+/**
+ * @file
+ * Handles AJAX submission and response in Views UI.
+ */
+
+(function ($, Drupal, drupalSettings) {
+
+  'use strict';
+
+  /**
+   * Ajax command for highlighting elements.
+   *
+   * @param {Drupal.Ajax} [ajax]
+   *   An Ajax object.
+   * @param {object} response
+   *   The Ajax response.
+   * @param {string} response.selector
+   *   The selector in question.
+   * @param {number} [status]
+   *   The HTTP status code.
+   */
+  Drupal.AjaxCommands.prototype.viewsHighlight = function (ajax, response, status) {
+    $('.hilited').removeClass('hilited');
+    $(response.selector).addClass('hilited');
+  };
+
+  /**
+   * Ajax command to set the form submit action in the views modal edit form.
+   *
+   * @param {Drupal.Ajax} [ajax]
+   *   An Ajax object.
+   * @param {object} response
+   *   The Ajax response. Contains .url
+   * @param {string} [status]
+   *   The XHR status code?
+   */
+  Drupal.AjaxCommands.prototype.viewsSetForm = function (ajax, response, status) {
+    var $form = $('.js-views-ui-dialog form');
+    // Identify the button that was clicked so that .ajaxSubmit() can use it.
+    // We need to do this for both .click() and .mousedown() since JavaScript
+    // code might trigger either behavior.
+    var $submit_buttons = $form.find('input[type=submit].js-form-submit, button.js-form-submit').once('views-ajax-submit');
+    $submit_buttons.on('click mousedown', function () {
+      this.form.clk = this;
+    });
+    $form.once('views-ajax-submit').each(function () {
+      var $form = $(this);
+      var element_settings = {
+        url: response.url,
+        event: 'submit',
+        base: $form.attr('id'),
+        element: this
+      };
+      var ajaxForm = Drupal.ajax(element_settings);
+      ajaxForm.$form = $form;
+    });
+  };
+
+  /**
+   * Ajax command to show certain buttons in the views edit form.
+   *
+   * @param {Drupal.Ajax} [ajax]
+   *   An Ajax object.
+   * @param {object} response
+   *   The Ajax response.
+   * @param {bool} response.changed
+   *   Whether the state changed for the buttons or not.
+   * @param {number} [status]
+   *   The HTTP status code.
+   */
+  Drupal.AjaxCommands.prototype.viewsShowButtons = function (ajax, response, status) {
+    $('div.views-edit-view div.form-actions').removeClass('js-hide');
+    if (response.changed) {
+      $('div.views-edit-view div.view-changed.messages').removeClass('js-hide');
+    }
+  };
+
+  /**
+   * Ajax command for triggering preview.
+   *
+   * @param {Drupal.Ajax} [ajax]
+   *   An Ajax object.
+   * @param {object} [response]
+   *   The Ajax response.
+   * @param {number} [status]
+   *   The HTTP status code.
+   */
+  Drupal.AjaxCommands.prototype.viewsTriggerPreview = function (ajax, response, status) {
+    if ($('input#edit-displays-live-preview').is(':checked')) {
+      $('#preview-submit').trigger('click');
+    }
+  };
+
+  /**
+   * Ajax command to replace the title of a page.
+   *
+   * @param {Drupal.Ajax} [ajax]
+   *   An Ajax object.
+   * @param {object} response
+   *   The Ajax response.
+   * @param {string} response.siteName
+   *   The site name.
+   * @param {string} response.title
+   *   The new page title.
+   * @param {number} [status]
+   *   The HTTP status code.
+   */
+  Drupal.AjaxCommands.prototype.viewsReplaceTitle = function (ajax, response, status) {
+    var doc = document;
+    // For the <title> element, make a best-effort attempt to replace the page
+    // title and leave the site name alone. If the theme doesn't use the site
+    // name in the <title> element, this will fail.
+    var oldTitle = doc.title;
+    // Escape the site name, in case it has special characters in it, so we can
+    // use it in our regex.
+    var escapedSiteName = response.siteName.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
+    var re = new RegExp('.+ (.) ' + escapedSiteName);
+    doc.title = oldTitle.replace(re, response.title + ' $1 ' + response.siteName);
+
+    $('h1.page-title').text(response.title);
+  };
+
+  /**
+   * Get rid of irritating tabledrag messages.
+   *
+   * @return {Array}
+   *   An array of messages. Always empty array, to get rid of the messages.
+   */
+  Drupal.theme.tableDragChangedWarning = function () {
+    return [];
+  };
+
+  /**
+   * Trigger preview when the "live preview" checkbox is checked.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches behavior to trigger live preview if the live preview option is
+   *   checked.
+   */
+  Drupal.behaviors.livePreview = {
+    attach: function (context) {
+      $('input#edit-displays-live-preview', context).once('views-ajax').on('click', function () {
+        if ($(this).is(':checked')) {
+          $('#preview-submit').trigger('click');
+        }
+      });
+    }
+  };
+
+  /**
+   * Sync preview display.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches behavior to sync the preview display when needed.
+   */
+  Drupal.behaviors.syncPreviewDisplay = {
+    attach: function (context) {
+      $('#views-tabset a').once('views-ajax').on('click', function () {
+        var href = $(this).attr('href');
+        // Cut of #views-tabset.
+        var display_id = href.substr(11);
+        // Set the form element.
+        $('#views-live-preview #preview-display-id').val(display_id);
+      });
+    }
+  };
+
+  /**
+   * Ajax behaviors for the views_ui module.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches ajax behaviors to the elements with the classes in question.
+   */
+  Drupal.behaviors.viewsAjax = {
+    collapseReplaced: false,
+    attach: function (context, settings) {
+      var base_element_settings = {
+        event: 'click',
+        progress: {type: 'fullscreen'}
+      };
+      // Bind AJAX behaviors to all items showing the class.
+      $('a.views-ajax-link', context).once('views-ajax').each(function () {
+        var element_settings = base_element_settings;
+        element_settings.base = $(this).attr('id');
+        element_settings.element = this;
+        // Set the URL to go to the anchor.
+        if ($(this).attr('href')) {
+          element_settings.url = $(this).attr('href');
+        }
+        Drupal.ajax(element_settings);
+      });
+
+      $('div#views-live-preview a')
+        .once('views-ajax').each(function () {
+          // We don't bind to links without a URL.
+          if (!$(this).attr('href')) {
+            return true;
+          }
+
+          var element_settings = base_element_settings;
+          // Set the URL to go to the anchor.
+          element_settings.url = $(this).attr('href');
+          if (Drupal.Views.getPath(element_settings.url).substring(0, 21) !== 'admin/structure/views') {
+            return true;
+          }
+
+          element_settings.wrapper = 'views-preview-wrapper';
+          element_settings.method = 'replaceWith';
+          element_settings.base = $(this).attr('id');
+          element_settings.element = this;
+          Drupal.ajax(element_settings);
+        });
+
+      // Within a live preview, make exposed widget form buttons re-trigger the
+      // Preview button.
+      // @todo Revisit this after fixing Views UI to display a Preview outside
+      //   of the main Edit form.
+      $('div#views-live-preview input[type=submit]')
+        .once('views-ajax').each(function (event) {
+          $(this).on('click', function () {
+            this.form.clk = this;
+            return true;
+          });
+          var element_settings = base_element_settings;
+          // Set the URL to go to the anchor.
+          element_settings.url = $(this.form).attr('action');
+          if (Drupal.Views.getPath(element_settings.url).substring(0, 21) !== 'admin/structure/views') {
+            return true;
+          }
+
+          element_settings.wrapper = 'views-preview-wrapper';
+          element_settings.method = 'replaceWith';
+          element_settings.event = 'click';
+          element_settings.base = $(this).attr('id');
+          element_settings.element = this;
+
+          Drupal.ajax(element_settings);
+        });
+
+    }
+  };
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/views_ui/js/ajax.js b/core/modules/views_ui/js/ajax.js
index d4ff7642f8fb..d2cf6d7eac76 100644
--- a/core/modules/views_ui/js/ajax.js
+++ b/core/modules/views_ui/js/ajax.js
@@ -1,44 +1,23 @@
 /**
- * @file
- * Handles AJAX submission and response in Views UI.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/views_ui/js/ajax.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings) {
 
   'use strict';
 
-  /**
-   * Ajax command for highlighting elements.
-   *
-   * @param {Drupal.Ajax} [ajax]
-   *   An Ajax object.
-   * @param {object} response
-   *   The Ajax response.
-   * @param {string} response.selector
-   *   The selector in question.
-   * @param {number} [status]
-   *   The HTTP status code.
-   */
   Drupal.AjaxCommands.prototype.viewsHighlight = function (ajax, response, status) {
     $('.hilited').removeClass('hilited');
     $(response.selector).addClass('hilited');
   };
 
-  /**
-   * Ajax command to set the form submit action in the views modal edit form.
-   *
-   * @param {Drupal.Ajax} [ajax]
-   *   An Ajax object.
-   * @param {object} response
-   *   The Ajax response. Contains .url
-   * @param {string} [status]
-   *   The XHR status code?
-   */
   Drupal.AjaxCommands.prototype.viewsSetForm = function (ajax, response, status) {
     var $form = $('.js-views-ui-dialog form');
-    // Identify the button that was clicked so that .ajaxSubmit() can use it.
-    // We need to do this for both .click() and .mousedown() since JavaScript
-    // code might trigger either behavior.
+
     var $submit_buttons = $form.find('input[type=submit].js-form-submit, button.js-form-submit').once('views-ajax-submit');
     $submit_buttons.on('click mousedown', function () {
       this.form.clk = this;
@@ -56,18 +35,6 @@
     });
   };
 
-  /**
-   * Ajax command to show certain buttons in the views edit form.
-   *
-   * @param {Drupal.Ajax} [ajax]
-   *   An Ajax object.
-   * @param {object} response
-   *   The Ajax response.
-   * @param {bool} response.changed
-   *   Whether the state changed for the buttons or not.
-   * @param {number} [status]
-   *   The HTTP status code.
-   */
   Drupal.AjaxCommands.prototype.viewsShowButtons = function (ajax, response, status) {
     $('div.views-edit-view div.form-actions').removeClass('js-hide');
     if (response.changed) {
@@ -75,44 +42,17 @@
     }
   };
 
-  /**
-   * Ajax command for triggering preview.
-   *
-   * @param {Drupal.Ajax} [ajax]
-   *   An Ajax object.
-   * @param {object} [response]
-   *   The Ajax response.
-   * @param {number} [status]
-   *   The HTTP status code.
-   */
   Drupal.AjaxCommands.prototype.viewsTriggerPreview = function (ajax, response, status) {
     if ($('input#edit-displays-live-preview').is(':checked')) {
       $('#preview-submit').trigger('click');
     }
   };
 
-  /**
-   * Ajax command to replace the title of a page.
-   *
-   * @param {Drupal.Ajax} [ajax]
-   *   An Ajax object.
-   * @param {object} response
-   *   The Ajax response.
-   * @param {string} response.siteName
-   *   The site name.
-   * @param {string} response.title
-   *   The new page title.
-   * @param {number} [status]
-   *   The HTTP status code.
-   */
   Drupal.AjaxCommands.prototype.viewsReplaceTitle = function (ajax, response, status) {
     var doc = document;
-    // For the <title> element, make a best-effort attempt to replace the page
-    // title and leave the site name alone. If the theme doesn't use the site
-    // name in the <title> element, this will fail.
+
     var oldTitle = doc.title;
-    // Escape the site name, in case it has special characters in it, so we can
-    // use it in our regex.
+
     var escapedSiteName = response.siteName.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
     var re = new RegExp('.+ (.) ' + escapedSiteName);
     doc.title = oldTitle.replace(re, response.title + ' $1 ' + response.siteName);
@@ -120,27 +60,12 @@
     $('h1.page-title').text(response.title);
   };
 
-  /**
-   * Get rid of irritating tabledrag messages.
-   *
-   * @return {Array}
-   *   An array of messages. Always empty array, to get rid of the messages.
-   */
   Drupal.theme.tableDragChangedWarning = function () {
     return [];
   };
 
-  /**
-   * Trigger preview when the "live preview" checkbox is checked.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches behavior to trigger live preview if the live preview option is
-   *   checked.
-   */
   Drupal.behaviors.livePreview = {
-    attach: function (context) {
+    attach: function attach(context) {
       $('input#edit-displays-live-preview', context).once('views-ajax').on('click', function () {
         if ($(this).is(':checked')) {
           $('#preview-submit').trigger('click');
@@ -149,101 +74,76 @@
     }
   };
 
-  /**
-   * Sync preview display.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches behavior to sync the preview display when needed.
-   */
   Drupal.behaviors.syncPreviewDisplay = {
-    attach: function (context) {
+    attach: function attach(context) {
       $('#views-tabset a').once('views-ajax').on('click', function () {
         var href = $(this).attr('href');
-        // Cut of #views-tabset.
+
         var display_id = href.substr(11);
-        // Set the form element.
+
         $('#views-live-preview #preview-display-id').val(display_id);
       });
     }
   };
 
-  /**
-   * Ajax behaviors for the views_ui module.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches ajax behaviors to the elements with the classes in question.
-   */
   Drupal.behaviors.viewsAjax = {
     collapseReplaced: false,
-    attach: function (context, settings) {
+    attach: function attach(context, settings) {
       var base_element_settings = {
         event: 'click',
-        progress: {type: 'fullscreen'}
+        progress: { type: 'fullscreen' }
       };
-      // Bind AJAX behaviors to all items showing the class.
+
       $('a.views-ajax-link', context).once('views-ajax').each(function () {
         var element_settings = base_element_settings;
         element_settings.base = $(this).attr('id');
         element_settings.element = this;
-        // Set the URL to go to the anchor.
+
         if ($(this).attr('href')) {
           element_settings.url = $(this).attr('href');
         }
         Drupal.ajax(element_settings);
       });
 
-      $('div#views-live-preview a')
-        .once('views-ajax').each(function () {
-          // We don't bind to links without a URL.
-          if (!$(this).attr('href')) {
-            return true;
-          }
+      $('div#views-live-preview a').once('views-ajax').each(function () {
+        if (!$(this).attr('href')) {
+          return true;
+        }
 
-          var element_settings = base_element_settings;
-          // Set the URL to go to the anchor.
-          element_settings.url = $(this).attr('href');
-          if (Drupal.Views.getPath(element_settings.url).substring(0, 21) !== 'admin/structure/views') {
-            return true;
-          }
-
-          element_settings.wrapper = 'views-preview-wrapper';
-          element_settings.method = 'replaceWith';
-          element_settings.base = $(this).attr('id');
-          element_settings.element = this;
-          Drupal.ajax(element_settings);
-        });
+        var element_settings = base_element_settings;
 
-      // Within a live preview, make exposed widget form buttons re-trigger the
-      // Preview button.
-      // @todo Revisit this after fixing Views UI to display a Preview outside
-      //   of the main Edit form.
-      $('div#views-live-preview input[type=submit]')
-        .once('views-ajax').each(function (event) {
-          $(this).on('click', function () {
-            this.form.clk = this;
-            return true;
-          });
-          var element_settings = base_element_settings;
-          // Set the URL to go to the anchor.
-          element_settings.url = $(this.form).attr('action');
-          if (Drupal.Views.getPath(element_settings.url).substring(0, 21) !== 'admin/structure/views') {
-            return true;
-          }
-
-          element_settings.wrapper = 'views-preview-wrapper';
-          element_settings.method = 'replaceWith';
-          element_settings.event = 'click';
-          element_settings.base = $(this).attr('id');
-          element_settings.element = this;
-
-          Drupal.ajax(element_settings);
+        element_settings.url = $(this).attr('href');
+        if (Drupal.Views.getPath(element_settings.url).substring(0, 21) !== 'admin/structure/views') {
+          return true;
+        }
+
+        element_settings.wrapper = 'views-preview-wrapper';
+        element_settings.method = 'replaceWith';
+        element_settings.base = $(this).attr('id');
+        element_settings.element = this;
+        Drupal.ajax(element_settings);
+      });
+
+      $('div#views-live-preview input[type=submit]').once('views-ajax').each(function (event) {
+        $(this).on('click', function () {
+          this.form.clk = this;
+          return true;
         });
+        var element_settings = base_element_settings;
+
+        element_settings.url = $(this.form).attr('action');
+        if (Drupal.Views.getPath(element_settings.url).substring(0, 21) !== 'admin/structure/views') {
+          return true;
+        }
 
+        element_settings.wrapper = 'views-preview-wrapper';
+        element_settings.method = 'replaceWith';
+        element_settings.event = 'click';
+        element_settings.base = $(this).attr('id');
+        element_settings.element = this;
+
+        Drupal.ajax(element_settings);
+      });
     }
   };
-
-})(jQuery, Drupal, drupalSettings);
+})(jQuery, Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/modules/views_ui/js/dialog.views.es6.js b/core/modules/views_ui/js/dialog.views.es6.js
new file mode 100644
index 000000000000..3f40b4ad5b9e
--- /dev/null
+++ b/core/modules/views_ui/js/dialog.views.es6.js
@@ -0,0 +1,58 @@
+/**
+ * @file
+ * Views dialog behaviors.
+ */
+
+(function ($, Drupal, drupalSettings) {
+
+  'use strict';
+
+  function handleDialogResize(e) {
+    var $modal = $(e.currentTarget);
+    var $viewsOverride = $modal.find('[data-drupal-views-offset]');
+    var $scroll = $modal.find('[data-drupal-views-scroll]');
+    var offset = 0;
+    var modalHeight;
+    if ($scroll.length) {
+      // Add a class to do some styles adjustments.
+      $modal.closest('.views-ui-dialog').addClass('views-ui-dialog-scroll');
+      // Let scroll element take all the height available.
+      $scroll.css({overflow: 'visible', height: 'auto'});
+      modalHeight = $modal.height();
+      $viewsOverride.each(function () { offset += $(this).outerHeight(); });
+
+      // Take internal padding into account.
+      var scrollOffset = $scroll.outerHeight() - $scroll.height();
+      $scroll.height(modalHeight - offset - scrollOffset);
+      // Reset scrolling properties.
+      $modal.css('overflow', 'hidden');
+      $scroll.css('overflow', 'auto');
+    }
+  }
+
+  /**
+   * Functionality for views modals.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches modal functionality for views.
+   * @prop {Drupal~behaviorDetach} detach
+   *   Detaches the modal functionality.
+   */
+  Drupal.behaviors.viewsModalContent = {
+    attach: function (context) {
+      $('body').once('viewsDialog').on('dialogContentResize.viewsDialog', '.ui-dialog-content', handleDialogResize);
+      // When expanding details, make sure the modal is resized.
+      $(context).find('.scroll').once('detailsUpdate').on('click', 'summary', function (e) {
+        $(e.currentTarget).trigger('dialogContentResize');
+      });
+    },
+    detach: function (context, settings, trigger) {
+      if (trigger === 'unload') {
+        $('body').removeOnce('viewsDialog').off('.viewsDialog');
+      }
+    }
+  };
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/views_ui/js/dialog.views.js b/core/modules/views_ui/js/dialog.views.js
index 3f40b4ad5b9e..a4c20bb33a20 100644
--- a/core/modules/views_ui/js/dialog.views.js
+++ b/core/modules/views_ui/js/dialog.views.js
@@ -1,7 +1,10 @@
 /**
- * @file
- * Views dialog behaviors.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/views_ui/js/dialog.views.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings) {
 
@@ -14,45 +17,34 @@
     var offset = 0;
     var modalHeight;
     if ($scroll.length) {
-      // Add a class to do some styles adjustments.
       $modal.closest('.views-ui-dialog').addClass('views-ui-dialog-scroll');
-      // Let scroll element take all the height available.
-      $scroll.css({overflow: 'visible', height: 'auto'});
+
+      $scroll.css({ overflow: 'visible', height: 'auto' });
       modalHeight = $modal.height();
-      $viewsOverride.each(function () { offset += $(this).outerHeight(); });
+      $viewsOverride.each(function () {
+        offset += $(this).outerHeight();
+      });
 
-      // Take internal padding into account.
       var scrollOffset = $scroll.outerHeight() - $scroll.height();
       $scroll.height(modalHeight - offset - scrollOffset);
-      // Reset scrolling properties.
+
       $modal.css('overflow', 'hidden');
       $scroll.css('overflow', 'auto');
     }
   }
 
-  /**
-   * Functionality for views modals.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches modal functionality for views.
-   * @prop {Drupal~behaviorDetach} detach
-   *   Detaches the modal functionality.
-   */
   Drupal.behaviors.viewsModalContent = {
-    attach: function (context) {
+    attach: function attach(context) {
       $('body').once('viewsDialog').on('dialogContentResize.viewsDialog', '.ui-dialog-content', handleDialogResize);
-      // When expanding details, make sure the modal is resized.
+
       $(context).find('.scroll').once('detailsUpdate').on('click', 'summary', function (e) {
         $(e.currentTarget).trigger('dialogContentResize');
       });
     },
-    detach: function (context, settings, trigger) {
+    detach: function detach(context, settings, trigger) {
       if (trigger === 'unload') {
         $('body').removeOnce('viewsDialog').off('.viewsDialog');
       }
     }
   };
-
-})(jQuery, Drupal, drupalSettings);
+})(jQuery, Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/modules/views_ui/js/views-admin.es6.js b/core/modules/views_ui/js/views-admin.es6.js
new file mode 100644
index 000000000000..0fda80874895
--- /dev/null
+++ b/core/modules/views_ui/js/views-admin.es6.js
@@ -0,0 +1,1192 @@
+/**
+ * @file
+ * Some basic behaviors and utility functions for Views UI.
+ */
+
+(function ($, Drupal, drupalSettings) {
+
+  'use strict';
+
+  /**
+   * @namespace
+   */
+  Drupal.viewsUi = {};
+
+  /**
+   * Improve the user experience of the views edit interface.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches toggling of SQL rewrite warning on the corresponding checkbox.
+   */
+  Drupal.behaviors.viewsUiEditView = {
+    attach: function () {
+      // Only show the SQL rewrite warning when the user has chosen the
+      // corresponding checkbox.
+      $('[data-drupal-selector="edit-query-options-disable-sql-rewrite"]').on('click', function () {
+        $('.sql-rewrite-warning').toggleClass('js-hide');
+      });
+    }
+  };
+
+  /**
+   * In the add view wizard, use the view name to prepopulate form fields such
+   * as page title and menu link.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches behavior for prepopulating page title and menu links, based on
+   *   view name.
+   */
+  Drupal.behaviors.viewsUiAddView = {
+    attach: function (context) {
+      var $context = $(context);
+      // Set up regular expressions to allow only numbers, letters, and dashes.
+      var exclude = new RegExp('[^a-z0-9\\-]+', 'g');
+      var replace = '-';
+      var suffix;
+
+      // The page title, block title, and menu link fields can all be
+      // prepopulated with the view name - no regular expression needed.
+      var $fields = $context.find('[id^="edit-page-title"], [id^="edit-block-title"], [id^="edit-page-link-properties-title"]');
+      if ($fields.length) {
+        if (!this.fieldsFiller) {
+          this.fieldsFiller = new Drupal.viewsUi.FormFieldFiller($fields);
+        }
+        else {
+          // After an AJAX response, this.fieldsFiller will still have event
+          // handlers bound to the old version of the form fields (which don't
+          // exist anymore). The event handlers need to be unbound and then
+          // rebound to the new markup. Note that jQuery.live is difficult to
+          // make work in this case because the IDs of the form fields change
+          // on every AJAX response.
+          this.fieldsFiller.rebind($fields);
+        }
+      }
+
+      // Prepopulate the path field with a URLified version of the view name.
+      var $pathField = $context.find('[id^="edit-page-path"]');
+      if ($pathField.length) {
+        if (!this.pathFiller) {
+          this.pathFiller = new Drupal.viewsUi.FormFieldFiller($pathField, exclude, replace);
+        }
+        else {
+          this.pathFiller.rebind($pathField);
+        }
+      }
+
+      // Populate the RSS feed field with a URLified version of the view name,
+      // and an .xml suffix (to make it unique).
+      var $feedField = $context.find('[id^="edit-page-feed-properties-path"]');
+      if ($feedField.length) {
+        if (!this.feedFiller) {
+          suffix = '.xml';
+          this.feedFiller = new Drupal.viewsUi.FormFieldFiller($feedField, exclude, replace, suffix);
+        }
+        else {
+          this.feedFiller.rebind($feedField);
+        }
+      }
+    }
+  };
+
+  /**
+   * Constructor for the {@link Drupal.viewsUi.FormFieldFiller} object.
+   *
+   * Prepopulates a form field based on the view name.
+   *
+   * @constructor
+   *
+   * @param {jQuery} $target
+   *   A jQuery object representing the form field or fields to prepopulate.
+   * @param {bool} [exclude=false]
+   *   A regular expression representing characters to exclude from
+   *   the target field.
+   * @param {string} [replace='']
+   *   A string to use as the replacement value for disallowed characters.
+   * @param {string} [suffix='']
+   *   A suffix to append at the end of the target field content.
+   */
+  Drupal.viewsUi.FormFieldFiller = function ($target, exclude, replace, suffix) {
+
+    /**
+     *
+     * @type {jQuery}
+     */
+    this.source = $('#edit-label');
+
+    /**
+     *
+     * @type {jQuery}
+     */
+    this.target = $target;
+
+    /**
+     *
+     * @type {bool}
+     */
+    this.exclude = exclude || false;
+
+    /**
+     *
+     * @type {string}
+     */
+    this.replace = replace || '';
+
+    /**
+     *
+     * @type {string}
+     */
+    this.suffix = suffix || '';
+
+    // Create bound versions of this instance's object methods to use as event
+    // handlers. This will let us easily unbind those specific handlers later
+    // on. NOTE: jQuery.proxy will not work for this because it assumes we want
+    // only one bound version of an object method, whereas we need one version
+    // per object instance.
+    var self = this;
+
+    /**
+     * Populate the target form field with the altered source field value.
+     *
+     * @return {*}
+     *   The result of the _populate call, which should be undefined.
+     */
+    this.populate = function () { return self._populate.call(self); };
+
+    /**
+     * Stop prepopulating the form fields.
+     *
+     * @return {*}
+     *   The result of the _unbind call, which should be undefined.
+     */
+    this.unbind = function () { return self._unbind.call(self); };
+
+    this.bind();
+    // Object constructor; no return value.
+  };
+
+  $.extend(Drupal.viewsUi.FormFieldFiller.prototype, /** @lends Drupal.viewsUi.FormFieldFiller# */{
+
+    /**
+     * Bind the form-filling behavior.
+     */
+    bind: function () {
+      this.unbind();
+      // Populate the form field when the source changes.
+      this.source.on('keyup.viewsUi change.viewsUi', this.populate);
+      // Quit populating the field as soon as it gets focus.
+      this.target.on('focus.viewsUi', this.unbind);
+    },
+
+    /**
+     * Get the source form field value as altered by the passed-in parameters.
+     *
+     * @return {string}
+     *   The source form field value.
+     */
+    getTransliterated: function () {
+      var from = this.source.val();
+      if (this.exclude) {
+        from = from.toLowerCase().replace(this.exclude, this.replace);
+      }
+      return from;
+    },
+
+    /**
+     * Populate the target form field with the altered source field value.
+     */
+    _populate: function () {
+      var transliterated = this.getTransliterated();
+      var suffix = this.suffix;
+      this.target.each(function (i) {
+        // Ensure that the maxlength is not exceeded by prepopulating the field.
+        var maxlength = $(this).attr('maxlength') - suffix.length;
+        $(this).val(transliterated.substr(0, maxlength) + suffix);
+      });
+    },
+
+    /**
+     * Stop prepopulating the form fields.
+     */
+    _unbind: function () {
+      this.source.off('keyup.viewsUi change.viewsUi', this.populate);
+      this.target.off('focus.viewsUi', this.unbind);
+    },
+
+    /**
+     * Bind event handlers to new form fields, after they're replaced via Ajax.
+     *
+     * @param {jQuery} $fields
+     *   Fields to rebind functionality to.
+     */
+    rebind: function ($fields) {
+      this.target = $fields;
+      this.bind();
+    }
+  });
+
+  /**
+   * Adds functionality for the add item form.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches the functionality in {@link Drupal.viewsUi.AddItemForm} to the
+   *   forms in question.
+   */
+  Drupal.behaviors.addItemForm = {
+    attach: function (context) {
+      var $context = $(context);
+      var $form = $context;
+      // The add handler form may have an id of views-ui-add-handler-form--n.
+      if (!$context.is('form[id^="views-ui-add-handler-form"]')) {
+        $form = $context.find('form[id^="views-ui-add-handler-form"]');
+      }
+      if ($form.once('views-ui-add-handler-form').length) {
+        // If we we have an unprocessed views-ui-add-handler-form, let's
+        // instantiate.
+        new Drupal.viewsUi.AddItemForm($form);
+      }
+    }
+  };
+
+  /**
+   * Constructs a new AddItemForm.
+   *
+   * @constructor
+   *
+   * @param {jQuery} $form
+   *   The form element used.
+   */
+  Drupal.viewsUi.AddItemForm = function ($form) {
+
+    /**
+     *
+     * @type {jQuery}
+     */
+    this.$form = $form;
+    this.$form.find('.views-filterable-options :checkbox').on('click', $.proxy(this.handleCheck, this));
+
+    /**
+     * Find the wrapper of the displayed text.
+     */
+    this.$selected_div = this.$form.find('.views-selected-options').parent();
+    this.$selected_div.hide();
+
+    /**
+     *
+     * @type {Array}
+     */
+    this.checkedItems = [];
+  };
+
+  /**
+   * Handles a checkbox check.
+   *
+   * @param {jQuery.Event} event
+   *   The event triggered.
+   */
+  Drupal.viewsUi.AddItemForm.prototype.handleCheck = function (event) {
+    var $target = $(event.target);
+    var label = $.trim($target.closest('td').next().html());
+    // Add/remove the checked item to the list.
+    if ($target.is(':checked')) {
+      this.$selected_div.show().css('display', 'block');
+      this.checkedItems.push(label);
+    }
+    else {
+      var position = $.inArray(label, this.checkedItems);
+      // Delete the item from the list and make sure that the list doesn't have
+      // undefined items left.
+      for (var i = 0; i < this.checkedItems.length; i++) {
+        if (i === position) {
+          this.checkedItems.splice(i, 1);
+          i--;
+          break;
+        }
+      }
+      // Hide it again if none item is selected.
+      if (this.checkedItems.length === 0) {
+        this.$selected_div.hide();
+      }
+    }
+    this.refreshCheckedItems();
+  };
+
+  /**
+   * Refresh the display of the checked items.
+   */
+  Drupal.viewsUi.AddItemForm.prototype.refreshCheckedItems = function () {
+    // Perhaps we should precache the text div, too.
+    this.$selected_div.find('.views-selected-options')
+      .html(this.checkedItems.join(', '))
+      .trigger('dialogContentResize');
+  };
+
+  /**
+   * The input field items that add displays must be rendered as `<input>`
+   * elements. The following behavior detaches the `<input>` elements from the
+   * DOM, wraps them in an unordered list, then appends them to the list of
+   * tabs.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Fixes the input elements needed.
+   */
+  Drupal.behaviors.viewsUiRenderAddViewButton = {
+    attach: function (context) {
+      // Build the add display menu and pull the display input buttons into it.
+      var $menu = $(context).find('#views-display-menu-tabs').once('views-ui-render-add-view-button');
+      if (!$menu.length) {
+        return;
+      }
+
+      var $addDisplayDropdown = $('<li class="add"><a href="#"><span class="icon add"></span>' + Drupal.t('Add') + '</a><ul class="action-list" style="display:none;"></ul></li>');
+      var $displayButtons = $menu.nextAll('input.add-display').detach();
+      $displayButtons.appendTo($addDisplayDropdown.find('.action-list')).wrap('<li>')
+        .parent().eq(0).addClass('first').end().eq(-1).addClass('last');
+      // Remove the 'Add ' prefix from the button labels since they're being
+      // placed in an 'Add' dropdown. @todo This assumes English, but so does
+      // $addDisplayDropdown above. Add support for translation.
+      $displayButtons.each(function () {
+        var label = $(this).val();
+        if (label.substr(0, 4) === 'Add ') {
+          $(this).val(label.substr(4));
+        }
+      });
+      $addDisplayDropdown.appendTo($menu);
+
+      // Add the click handler for the add display button.
+      $menu.find('li.add > a').on('click', function (event) {
+        event.preventDefault();
+        var $trigger = $(this);
+        Drupal.behaviors.viewsUiRenderAddViewButton.toggleMenu($trigger);
+      });
+      // Add a mouseleave handler to close the dropdown when the user mouses
+      // away from the item. We use mouseleave instead of mouseout because
+      // the user is going to trigger mouseout when she moves from the trigger
+      // link to the sub menu items.
+      // We use the live binder because the open class on this item will be
+      // toggled on and off and we want the handler to take effect in the cases
+      // that the class is present, but not when it isn't.
+      $('li.add', $menu).on('mouseleave', function (event) {
+        var $this = $(this);
+        var $trigger = $this.children('a[href="#"]');
+        if ($this.children('.action-list').is(':visible')) {
+          Drupal.behaviors.viewsUiRenderAddViewButton.toggleMenu($trigger);
+        }
+      });
+    }
+  };
+
+  /**
+   * Toggle menu visibility.
+   *
+   * @param {jQuery} $trigger
+   *   The element where the toggle was triggered.
+   *
+   *
+   * @note [@jessebeach] I feel like the following should be a more generic
+   *   function and not written specifically for this UI, but I'm not sure
+   *   where to put it.
+   */
+  Drupal.behaviors.viewsUiRenderAddViewButton.toggleMenu = function ($trigger) {
+    $trigger.parent().toggleClass('open');
+    $trigger.next().slideToggle('fast');
+  };
+
+  /**
+   * Add search options to the views ui.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches {@link Drupal.viewsUi.OptionsSearch} to the views ui filter
+   *   options.
+   */
+  Drupal.behaviors.viewsUiSearchOptions = {
+    attach: function (context) {
+      var $context = $(context);
+      var $form = $context;
+      // The add handler form may have an id of views-ui-add-handler-form--n.
+      if (!$context.is('form[id^="views-ui-add-handler-form"]')) {
+        $form = $context.find('form[id^="views-ui-add-handler-form"]');
+      }
+      // Make sure we don't add more than one event handler to the same form.
+      if ($form.once('views-ui-filter-options').length) {
+        new Drupal.viewsUi.OptionsSearch($form);
+      }
+    }
+  };
+
+  /**
+   * Constructor for the viewsUi.OptionsSearch object.
+   *
+   * The OptionsSearch object filters the available options on a form according
+   * to the user's search term. Typing in "taxonomy" will show only those
+   * options containing "taxonomy" in their label.
+   *
+   * @constructor
+   *
+   * @param {jQuery} $form
+   *   The form element.
+   */
+  Drupal.viewsUi.OptionsSearch = function ($form) {
+
+    /**
+     *
+     * @type {jQuery}
+     */
+    this.$form = $form;
+
+    // Click on the title checks the box.
+    this.$form.on('click', 'td.title', function (event) {
+      var $target = $(event.currentTarget);
+      $target.closest('tr').find('input').trigger('click');
+    });
+
+    var searchBoxSelector = '[data-drupal-selector="edit-override-controls-options-search"]';
+    var controlGroupSelector = '[data-drupal-selector="edit-override-controls-group"]';
+    this.$form.on('formUpdated', searchBoxSelector + ',' + controlGroupSelector, $.proxy(this.handleFilter, this));
+
+    this.$searchBox = this.$form.find(searchBoxSelector);
+    this.$controlGroup = this.$form.find(controlGroupSelector);
+
+    /**
+     * Get a list of option labels and their corresponding divs and maintain it
+     * in memory, so we have as little overhead as possible at keyup time.
+     */
+    this.options = this.getOptions(this.$form.find('.filterable-option'));
+
+    // Trap the ENTER key in the search box so that it doesn't submit the form.
+    this.$searchBox.on('keypress', function (event) {
+      if (event.which === 13) {
+        event.preventDefault();
+      }
+    });
+  };
+
+  $.extend(Drupal.viewsUi.OptionsSearch.prototype, /** @lends Drupal.viewsUi.OptionsSearch# */{
+
+    /**
+     * Assemble a list of all the filterable options on the form.
+     *
+     * @param {jQuery} $allOptions
+     *   A jQuery object representing the rows of filterable options to be
+     *   shown and hidden depending on the user's search terms.
+     *
+     * @return {Array}
+     *   An array of all the filterable options.
+     */
+    getOptions: function ($allOptions) {
+      var $title;
+      var $description;
+      var $option;
+      var options = [];
+      var length = $allOptions.length;
+      for (var i = 0; i < length; i++) {
+        $option = $($allOptions[i]);
+        $title = $option.find('.title');
+        $description = $option.find('.description');
+        options[i] = {
+          // Search on the lowercase version of the title text + description.
+          searchText: $title.text().toLowerCase() + ' ' + $description.text().toLowerCase(),
+          // Maintain a reference to the jQuery object for each row, so we don't
+          // have to create a new object inside the performance-sensitive keyup
+          // handler.
+          $div: $option
+        };
+      }
+      return options;
+    },
+
+    /**
+     * Filter handler for the search box and type select that hides or shows the relevant
+     * options.
+     *
+     * @param {jQuery.Event} event
+     *   The formUpdated event.
+     */
+    handleFilter: function (event) {
+      // Determine the user's search query. The search text has been converted
+      // to lowercase.
+      var search = this.$searchBox.val().toLowerCase();
+      var words = search.split(' ');
+      // Get selected Group
+      var group = this.$controlGroup.val();
+
+      // Search through the search texts in the form for matching text.
+      this.options.forEach(function (option) {
+        function hasWord(word) {
+          return option.searchText.indexOf(word) !== -1;
+        }
+
+        var found = true;
+        // Each word in the search string has to match the item in order for the
+        // item to be shown.
+        if (search) {
+          found = words.every(hasWord);
+        }
+        if (found && group !== 'all') {
+          found = option.$div.hasClass(group);
+        }
+
+        option.$div.toggle(found);
+      });
+
+      // Adapt dialog to content size.
+      $(event.target).trigger('dialogContentResize');
+    }
+  });
+
+  /**
+   * Preview functionality in the views edit form.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches the preview functionality to the view edit form.
+   */
+  Drupal.behaviors.viewsUiPreview = {
+    attach: function (context) {
+      // Only act on the edit view form.
+      var $contextualFiltersBucket = $(context).find('.views-display-column .views-ui-display-tab-bucket.argument');
+      if ($contextualFiltersBucket.length === 0) {
+        return;
+      }
+
+      // If the display has no contextual filters, hide the form where you
+      // enter the contextual filters for the live preview. If it has contextual
+      // filters, show the form.
+      var $contextualFilters = $contextualFiltersBucket.find('.views-display-setting a');
+      if ($contextualFilters.length) {
+        $('#preview-args').parent().show();
+      }
+      else {
+        $('#preview-args').parent().hide();
+      }
+
+      // Executes an initial preview.
+      if ($('#edit-displays-live-preview').once('edit-displays-live-preview').is(':checked')) {
+        $('#preview-submit').once('edit-displays-live-preview').trigger('click');
+      }
+    }
+  };
+
+  /**
+   * Rearranges the filters.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attach handlers to make it possible to rearange the filters in the form
+   *   in question.
+   *   @see Drupal.viewsUi.RearrangeFilterHandler
+   */
+  Drupal.behaviors.viewsUiRearrangeFilter = {
+    attach: function (context) {
+      // Only act on the rearrange filter form.
+      if (typeof Drupal.tableDrag === 'undefined' || typeof Drupal.tableDrag['views-rearrange-filters'] === 'undefined') {
+        return;
+      }
+      var $context = $(context);
+      var $table = $context.find('#views-rearrange-filters').once('views-rearrange-filters');
+      var $operator = $context.find('.js-form-item-filter-groups-operator').once('views-rearrange-filters');
+      if ($table.length) {
+        new Drupal.viewsUi.RearrangeFilterHandler($table, $operator);
+      }
+    }
+  };
+
+  /**
+   * Improve the UI of the rearrange filters dialog box.
+   *
+   * @constructor
+   *
+   * @param {jQuery} $table
+   *   The table in the filter form.
+   * @param {jQuery} $operator
+   *   The filter groups operator element.
+   */
+  Drupal.viewsUi.RearrangeFilterHandler = function ($table, $operator) {
+
+    /**
+     * Keep a reference to the `<table>` being altered and to the div containing
+     * the filter groups operator dropdown (if it exists).
+     */
+    this.table = $table;
+
+    /**
+     *
+     * @type {jQuery}
+     */
+    this.operator = $operator;
+
+    /**
+     *
+     * @type {bool}
+     */
+    this.hasGroupOperator = this.operator.length > 0;
+
+    /**
+     * Keep a reference to all draggable rows within the table.
+     *
+     * @type {jQuery}
+     */
+    this.draggableRows = $table.find('.draggable');
+
+    /**
+     * Keep a reference to the buttons for adding and removing filter groups.
+     *
+     * @type {jQuery}
+     */
+    this.addGroupButton = $('input#views-add-group');
+
+    /**
+     * @type {jQuery}
+     */
+    this.removeGroupButtons = $table.find('input.views-remove-group');
+
+    // Add links that duplicate the functionality of the (hidden) add and remove
+    // buttons.
+    this.insertAddRemoveFilterGroupLinks();
+
+    // When there is a filter groups operator dropdown on the page, create
+    // duplicates of the dropdown between each pair of filter groups.
+    if (this.hasGroupOperator) {
+
+      /**
+       * @type {jQuery}
+       */
+      this.dropdowns = this.duplicateGroupsOperator();
+      this.syncGroupsOperators();
+    }
+
+    // Add methods to the tableDrag instance to account for operator cells
+    // (which span multiple rows), the operator labels next to each filter
+    // (e.g., "And" or "Or"), the filter groups, and other special aspects of
+    // this tableDrag instance.
+    this.modifyTableDrag();
+
+    // Initialize the operator labels (e.g., "And" or "Or") that are displayed
+    // next to the filters in each group, and bind a handler so that they change
+    // based on the values of the operator dropdown within that group.
+    this.redrawOperatorLabels();
+    $table.find('.views-group-title select')
+      .once('views-rearrange-filter-handler')
+      .on('change.views-rearrange-filter-handler', $.proxy(this, 'redrawOperatorLabels'));
+
+    // Bind handlers so that when a "Remove" link is clicked, we:
+    // - Update the rowspans of cells containing an operator dropdown (since
+    //   they need to change to reflect the number of rows in each group).
+    // - Redraw the operator labels next to the filters in the group (since the
+    //   filter that is currently displayed last in each group is not supposed
+    //   to have a label display next to it).
+    $table.find('a.views-groups-remove-link')
+      .once('views-rearrange-filter-handler')
+      .on('click.views-rearrange-filter-handler', $.proxy(this, 'updateRowspans'))
+      .on('click.views-rearrange-filter-handler', $.proxy(this, 'redrawOperatorLabels'));
+  };
+
+  $.extend(Drupal.viewsUi.RearrangeFilterHandler.prototype, /** @lends Drupal.viewsUi.RearrangeFilterHandler# */{
+
+    /**
+     * Insert links that allow filter groups to be added and removed.
+     */
+    insertAddRemoveFilterGroupLinks: function () {
+
+      // Insert a link for adding a new group at the top of the page, and make
+      // it match the action link styling used in a typical page.html.twig.
+      // Since Drupal does not provide a theme function for this markup this is
+      // the best we can do.
+      $('<ul class="action-links"><li><a id="views-add-group-link" href="#">' + this.addGroupButton.val() + '</a></li></ul>')
+        .prependTo(this.table.parent())
+        // When the link is clicked, dynamically click the hidden form button
+        // for adding a new filter group.
+        .once('views-rearrange-filter-handler')
+        .find('#views-add-group-link')
+        .on('click.views-rearrange-filter-handler', $.proxy(this, 'clickAddGroupButton'));
+
+      // Find each (visually hidden) button for removing a filter group and
+      // insert a link next to it.
+      var length = this.removeGroupButtons.length;
+      var i;
+      for (i = 0; i < length; i++) {
+        var $removeGroupButton = $(this.removeGroupButtons[i]);
+        var buttonId = $removeGroupButton.attr('id');
+        $('<a href="#" class="views-remove-group-link">' + Drupal.t('Remove group') + '</a>')
+          .insertBefore($removeGroupButton)
+          // When the link is clicked, dynamically click the corresponding form
+          // button.
+          .once('views-rearrange-filter-handler')
+          .on('click.views-rearrange-filter-handler', {buttonId: buttonId}, $.proxy(this, 'clickRemoveGroupButton'));
+      }
+    },
+
+    /**
+     * Dynamically click the button that adds a new filter group.
+     *
+     * @param {jQuery.Event} event
+     *   The event triggered.
+     */
+    clickAddGroupButton: function (event) {
+      this.addGroupButton.trigger('mousedown');
+      event.preventDefault();
+    },
+
+    /**
+     * Dynamically click a button for removing a filter group.
+     *
+     * @param {jQuery.Event} event
+     *   Event being triggered, with event.data.buttonId set to the ID of the
+     *   form button that should be clicked.
+     */
+    clickRemoveGroupButton: function (event) {
+      this.table.find('#' + event.data.buttonId).trigger('mousedown');
+      event.preventDefault();
+    },
+
+    /**
+     * Move the groups operator so that it's between the first two groups, and
+     * duplicate it between any subsequent groups.
+     *
+     * @return {jQuery}
+     *   An operator element.
+     */
+    duplicateGroupsOperator: function () {
+      var dropdowns;
+      var newRow;
+      var titleRow;
+
+      var titleRows = $('tr.views-group-title').once('duplicateGroupsOperator');
+
+      if (!titleRows.length) {
+        return this.operator;
+      }
+
+      // Get rid of the explanatory text around the operator; its placement is
+      // explanatory enough.
+      this.operator.find('label').add('div.description').addClass('visually-hidden');
+      this.operator.find('select').addClass('form-select');
+
+      // Keep a list of the operator dropdowns, so we can sync their behavior
+      // later.
+      dropdowns = this.operator;
+
+      // Move the operator to a new row just above the second group.
+      titleRow = $('tr#views-group-title-2');
+      newRow = $('<tr class="filter-group-operator-row"><td colspan="5"></td></tr>');
+      newRow.find('td').append(this.operator);
+      newRow.insertBefore(titleRow);
+      var length = titleRows.length;
+      // Starting with the third group, copy the operator to a new row above the
+      // group title.
+      for (var i = 2; i < length; i++) {
+        titleRow = $(titleRows[i]);
+        // Make a copy of the operator dropdown and put it in a new table row.
+        var fakeOperator = this.operator.clone();
+        fakeOperator.attr('id', '');
+        newRow = $('<tr class="filter-group-operator-row"><td colspan="5"></td></tr>');
+        newRow.find('td').append(fakeOperator);
+        newRow.insertBefore(titleRow);
+        dropdowns.add(fakeOperator);
+      }
+
+      return dropdowns;
+    },
+
+    /**
+     * Make the duplicated groups operators change in sync with each other.
+     */
+    syncGroupsOperators: function () {
+      if (this.dropdowns.length < 2) {
+        // We only have one dropdown (or none at all), so there's nothing to
+        // sync.
+        return;
+      }
+
+      this.dropdowns.on('change', $.proxy(this, 'operatorChangeHandler'));
+    },
+
+    /**
+     * Click handler for the operators that appear between filter groups.
+     *
+     * Forces all operator dropdowns to have the same value.
+     *
+     * @param {jQuery.Event} event
+     *   The event triggered.
+     */
+    operatorChangeHandler: function (event) {
+      var $target = $(event.target);
+      var operators = this.dropdowns.find('select').not($target);
+
+      // Change the other operators to match this new value.
+      operators.val($target.val());
+    },
+
+    /**
+     * @method
+     */
+    modifyTableDrag: function () {
+      var tableDrag = Drupal.tableDrag['views-rearrange-filters'];
+      var filterHandler = this;
+
+      /**
+       * Override the row.onSwap method from tabledrag.js.
+       *
+       * When a row is dragged to another place in the table, several things
+       * need to occur.
+       * - The row needs to be moved so that it's within one of the filter
+       * groups.
+       * - The operator cells that span multiple rows need their rowspan
+       * attributes updated to reflect the number of rows in each group.
+       * - The operator labels that are displayed next to each filter need to
+       * be redrawn, to account for the row's new location.
+       */
+      tableDrag.row.prototype.onSwap = function () {
+        if (filterHandler.hasGroupOperator) {
+          // Make sure the row that just got moved (this.group) is inside one
+          // of the filter groups (i.e. below an empty marker row or a
+          // draggable). If it isn't, move it down one.
+          var thisRow = $(this.group);
+          var previousRow = thisRow.prev('tr');
+          if (previousRow.length && !previousRow.hasClass('group-message') && !previousRow.hasClass('draggable')) {
+            // Move the dragged row down one.
+            var next = thisRow.next();
+            if (next.is('tr')) {
+              this.swap('after', next);
+            }
+          }
+          filterHandler.updateRowspans();
+        }
+        // Redraw the operator labels that are displayed next to each filter, to
+        // account for the row's new location.
+        filterHandler.redrawOperatorLabels();
+      };
+
+      /**
+       * Override the onDrop method from tabledrag.js.
+       */
+      tableDrag.onDrop = function () {
+        // If the tabledrag change marker (i.e., the "*") has been inserted
+        // inside a row after the operator label (i.e., "And" or "Or")
+        // rearrange the items so the operator label continues to appear last.
+        var changeMarker = $(this.oldRowElement).find('.tabledrag-changed');
+        if (changeMarker.length) {
+          // Search for occurrences of the operator label before the change
+          // marker, and reverse them.
+          var operatorLabel = changeMarker.prevAll('.views-operator-label');
+          if (operatorLabel.length) {
+            operatorLabel.insertAfter(changeMarker);
+          }
+        }
+
+        // Make sure the "group" dropdown is properly updated when rows are
+        // dragged into an empty filter group. This is borrowed heavily from
+        // the block.js implementation of tableDrag.onDrop().
+        var groupRow = $(this.rowObject.element).prevAll('tr.group-message').get(0);
+        var groupName = groupRow.className.replace(/([^ ]+[ ]+)*group-([^ ]+)-message([ ]+[^ ]+)*/, '$2');
+        var groupField = $('select.views-group-select', this.rowObject.element);
+        if ($(this.rowObject.element).prev('tr').is('.group-message') && !groupField.is('.views-group-select-' + groupName)) {
+          var oldGroupName = groupField.attr('class').replace(/([^ ]+[ ]+)*views-group-select-([^ ]+)([ ]+[^ ]+)*/, '$2');
+          groupField.removeClass('views-group-select-' + oldGroupName).addClass('views-group-select-' + groupName);
+          groupField.val(groupName);
+        }
+      };
+    },
+
+    /**
+     * Redraw the operator labels that are displayed next to each filter.
+     */
+    redrawOperatorLabels: function () {
+      for (var i = 0; i < this.draggableRows.length; i++) {
+        // Within the row, the operator labels are displayed inside the first
+        // table cell (next to the filter name).
+        var $draggableRow = $(this.draggableRows[i]);
+        var $firstCell = $draggableRow.find('td').eq(0);
+        if ($firstCell.length) {
+          // The value of the operator label ("And" or "Or") is taken from the
+          // first operator dropdown we encounter, going backwards from the
+          // current row. This dropdown is the one associated with the current
+          // row's filter group.
+          var operatorValue = $draggableRow.prevAll('.views-group-title').find('option:selected').html();
+          var operatorLabel = '<span class="views-operator-label">' + operatorValue + '</span>';
+          // If the next visible row after this one is a draggable filter row,
+          // display the operator label next to the current row. (Checking for
+          // visibility is necessary here since the "Remove" links hide the
+          // removed row but don't actually remove it from the document).
+          var $nextRow = $draggableRow.nextAll(':visible').eq(0);
+          var $existingOperatorLabel = $firstCell.find('.views-operator-label');
+          if ($nextRow.hasClass('draggable')) {
+            // If an operator label was already there, replace it with the new
+            // one.
+            if ($existingOperatorLabel.length) {
+              $existingOperatorLabel.replaceWith(operatorLabel);
+            }
+            // Otherwise, append the operator label to the end of the table
+            // cell.
+            else {
+              $firstCell.append(operatorLabel);
+            }
+          }
+          // If the next row doesn't contain a filter, then this is the last row
+          // in the group. We don't want to display the operator there (since
+          // operators should only display between two related filters, e.g.
+          // "filter1 AND filter2 AND filter3"). So we remove any existing label
+          // that this row has.
+          else {
+            $existingOperatorLabel.remove();
+          }
+        }
+      }
+    },
+
+    /**
+     * Update the rowspan attribute of each cell containing an operator
+     * dropdown.
+     */
+    updateRowspans: function () {
+      var $row;
+      var $currentEmptyRow;
+      var draggableCount;
+      var $operatorCell;
+      var rows = $(this.table).find('tr');
+      var length = rows.length;
+      for (var i = 0; i < length; i++) {
+        $row = $(rows[i]);
+        if ($row.hasClass('views-group-title')) {
+          // This row is a title row.
+          // Keep a reference to the cell containing the dropdown operator.
+          $operatorCell = $row.find('td.group-operator');
+          // Assume this filter group is empty, until we find otherwise.
+          draggableCount = 0;
+          $currentEmptyRow = $row.next('tr');
+          $currentEmptyRow.removeClass('group-populated').addClass('group-empty');
+          // The cell with the dropdown operator should span the title row and
+          // the "this group is empty" row.
+          $operatorCell.attr('rowspan', 2);
+        }
+        else if ($row.hasClass('draggable') && $row.is(':visible')) {
+          // We've found a visible filter row, so we now know the group isn't
+          // empty.
+          draggableCount++;
+          $currentEmptyRow.removeClass('group-empty').addClass('group-populated');
+          // The operator cell should span all draggable rows, plus the title.
+          $operatorCell.attr('rowspan', draggableCount + 1);
+        }
+      }
+    }
+  });
+
+  /**
+   * Add a select all checkbox, which checks each checkbox at once.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches select all functionality to the views filter form.
+   */
+  Drupal.behaviors.viewsFilterConfigSelectAll = {
+    attach: function (context) {
+      var $context = $(context);
+
+      var $selectAll = $context.find('.js-form-item-options-value-all').once('filterConfigSelectAll');
+      var $selectAllCheckbox = $selectAll.find('input[type=checkbox]');
+      var $checkboxes = $selectAll.closest('.form-checkboxes').find('.js-form-type-checkbox:not(.js-form-item-options-value-all) input[type="checkbox"]');
+
+      if ($selectAll.length) {
+         // Show the select all checkbox.
+        $selectAll.show();
+        $selectAllCheckbox.on('click', function () {
+          // Update all checkbox beside the select all checkbox.
+          $checkboxes.prop('checked', $(this).is(':checked'));
+        });
+
+        // Uncheck the select all checkbox if any of the others are unchecked.
+        $checkboxes.on('click', function () {
+          if ($(this).is('checked') === false) {
+            $selectAllCheckbox.prop('checked', false);
+          }
+        });
+      }
+    }
+  };
+
+  /**
+   * Remove icon class from elements that are themed as buttons or dropbuttons.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Removes the icon class from certain views elements.
+   */
+  Drupal.behaviors.viewsRemoveIconClass = {
+    attach: function (context) {
+      $(context).find('.dropbutton').once('dropbutton-icon').find('.icon').removeClass('icon');
+    }
+  };
+
+  /**
+   * Change "Expose filter" buttons into checkboxes.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Changes buttons into checkboxes via {@link Drupal.viewsUi.Checkboxifier}.
+   */
+  Drupal.behaviors.viewsUiCheckboxify = {
+    attach: function (context, settings) {
+      var $buttons = $('[data-drupal-selector="edit-options-expose-button-button"], [data-drupal-selector="edit-options-group-button-button"]').once('views-ui-checkboxify');
+      var length = $buttons.length;
+      var i;
+      for (i = 0; i < length; i++) {
+        new Drupal.viewsUi.Checkboxifier($buttons[i]);
+      }
+    }
+  };
+
+  /**
+   * Change the default widget to select the default group according to the
+   * selected widget for the exposed group.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Changes the default widget based on user input.
+   */
+  Drupal.behaviors.viewsUiChangeDefaultWidget = {
+    attach: function (context) {
+      var $context = $(context);
+
+      function changeDefaultWidget(event) {
+        if ($(event.target).prop('checked')) {
+          $context.find('input.default-radios').parent().hide();
+          $context.find('td.any-default-radios-row').parent().hide();
+          $context.find('input.default-checkboxes').parent().show();
+        }
+        else {
+          $context.find('input.default-checkboxes').parent().hide();
+          $context.find('td.any-default-radios-row').parent().show();
+          $context.find('input.default-radios').parent().show();
+        }
+      }
+
+      // Update on widget change.
+      $context.find('input[name="options[group_info][multiple]"]')
+        .on('change', changeDefaultWidget)
+        // Update the first time the form is rendered.
+        .trigger('change');
+    }
+  };
+
+  /**
+   * Attaches expose filter button to a checkbox that triggers its click event.
+   *
+   * @constructor
+   *
+   * @param {HTMLElement} button
+   *   The DOM object representing the button to be checkboxified.
+   */
+  Drupal.viewsUi.Checkboxifier = function (button) {
+    this.$button = $(button);
+    this.$parent = this.$button.parent('div.views-expose, div.views-grouped');
+    this.$input = this.$parent.find('input:checkbox, input:radio');
+    // Hide the button and its description.
+    this.$button.hide();
+    this.$parent.find('.exposed-description, .grouped-description').hide();
+
+    this.$input.on('click', $.proxy(this, 'clickHandler'));
+
+  };
+
+  /**
+   * When the checkbox is checked or unchecked, simulate a button press.
+   *
+   * @param {jQuery.Event} e
+   *   The event triggered.
+   */
+  Drupal.viewsUi.Checkboxifier.prototype.clickHandler = function (e) {
+    this.$button
+      .trigger('click')
+      .trigger('submit');
+  };
+
+  /**
+   * Change the Apply button text based upon the override select state.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches behavior to change the Apply button according to the current
+   *   state.
+   */
+  Drupal.behaviors.viewsUiOverrideSelect = {
+    attach: function (context) {
+      $(context).find('[data-drupal-selector="edit-override-dropdown"]').once('views-ui-override-button-text').each(function () {
+        // Closures! :(
+        var $context = $(context);
+        var $submit = $context.find('[id^=edit-submit]');
+        var old_value = $submit.val();
+
+        $submit.once('views-ui-override-button-text')
+          .on('mouseup', function () {
+            $(this).val(old_value);
+            return true;
+          });
+
+        $(this).on('change', function () {
+          var $this = $(this);
+          if ($this.val() === 'default') {
+            $submit.val(Drupal.t('Apply (all displays)'));
+          }
+          else if ($this.val() === 'default_revert') {
+            $submit.val(Drupal.t('Revert to default'));
+          }
+          else {
+            $submit.val(Drupal.t('Apply (this display)'));
+          }
+          var $dialog = $context.closest('.ui-dialog-content');
+          $dialog.trigger('dialogButtonsChange');
+        })
+          .trigger('change');
+      });
+
+    }
+  };
+
+  /**
+   * Functionality for the remove link in the views UI.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches behavior for the remove view and remove display links.
+   */
+  Drupal.behaviors.viewsUiHandlerRemoveLink = {
+    attach: function (context) {
+      var $context = $(context);
+      // Handle handler deletion by looking for the hidden checkbox and hiding
+      // the row.
+      $context.find('a.views-remove-link').once('views').on('click', function (event) {
+        var id = $(this).attr('id').replace('views-remove-link-', '');
+        $context.find('#views-row-' + id).hide();
+        $context.find('#views-removed-' + id).prop('checked', true);
+        event.preventDefault();
+      });
+
+      // Handle display deletion by looking for the hidden checkbox and hiding
+      // the row.
+      $context.find('a.display-remove-link').once('display').on('click', function (event) {
+        var id = $(this).attr('id').replace('display-remove-link-', '');
+        $context.find('#display-row-' + id).hide();
+        $context.find('#display-removed-' + id).prop('checked', true);
+        event.preventDefault();
+      });
+    }
+  };
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/views_ui/js/views-admin.js b/core/modules/views_ui/js/views-admin.js
index 0fda80874895..da3e6c1e9b45 100644
--- a/core/modules/views_ui/js/views-admin.js
+++ b/core/modules/views_ui/js/views-admin.js
@@ -1,193 +1,97 @@
 /**
- * @file
- * Some basic behaviors and utility functions for Views UI.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/views_ui/js/views-admin.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal, drupalSettings) {
 
   'use strict';
 
-  /**
-   * @namespace
-   */
   Drupal.viewsUi = {};
 
-  /**
-   * Improve the user experience of the views edit interface.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches toggling of SQL rewrite warning on the corresponding checkbox.
-   */
   Drupal.behaviors.viewsUiEditView = {
-    attach: function () {
-      // Only show the SQL rewrite warning when the user has chosen the
-      // corresponding checkbox.
+    attach: function attach() {
       $('[data-drupal-selector="edit-query-options-disable-sql-rewrite"]').on('click', function () {
         $('.sql-rewrite-warning').toggleClass('js-hide');
       });
     }
   };
 
-  /**
-   * In the add view wizard, use the view name to prepopulate form fields such
-   * as page title and menu link.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches behavior for prepopulating page title and menu links, based on
-   *   view name.
-   */
   Drupal.behaviors.viewsUiAddView = {
-    attach: function (context) {
+    attach: function attach(context) {
       var $context = $(context);
-      // Set up regular expressions to allow only numbers, letters, and dashes.
+
       var exclude = new RegExp('[^a-z0-9\\-]+', 'g');
       var replace = '-';
       var suffix;
 
-      // The page title, block title, and menu link fields can all be
-      // prepopulated with the view name - no regular expression needed.
       var $fields = $context.find('[id^="edit-page-title"], [id^="edit-block-title"], [id^="edit-page-link-properties-title"]');
       if ($fields.length) {
         if (!this.fieldsFiller) {
           this.fieldsFiller = new Drupal.viewsUi.FormFieldFiller($fields);
-        }
-        else {
-          // After an AJAX response, this.fieldsFiller will still have event
-          // handlers bound to the old version of the form fields (which don't
-          // exist anymore). The event handlers need to be unbound and then
-          // rebound to the new markup. Note that jQuery.live is difficult to
-          // make work in this case because the IDs of the form fields change
-          // on every AJAX response.
+        } else {
           this.fieldsFiller.rebind($fields);
         }
       }
 
-      // Prepopulate the path field with a URLified version of the view name.
       var $pathField = $context.find('[id^="edit-page-path"]');
       if ($pathField.length) {
         if (!this.pathFiller) {
           this.pathFiller = new Drupal.viewsUi.FormFieldFiller($pathField, exclude, replace);
-        }
-        else {
+        } else {
           this.pathFiller.rebind($pathField);
         }
       }
 
-      // Populate the RSS feed field with a URLified version of the view name,
-      // and an .xml suffix (to make it unique).
       var $feedField = $context.find('[id^="edit-page-feed-properties-path"]');
       if ($feedField.length) {
         if (!this.feedFiller) {
           suffix = '.xml';
           this.feedFiller = new Drupal.viewsUi.FormFieldFiller($feedField, exclude, replace, suffix);
-        }
-        else {
+        } else {
           this.feedFiller.rebind($feedField);
         }
       }
     }
   };
 
-  /**
-   * Constructor for the {@link Drupal.viewsUi.FormFieldFiller} object.
-   *
-   * Prepopulates a form field based on the view name.
-   *
-   * @constructor
-   *
-   * @param {jQuery} $target
-   *   A jQuery object representing the form field or fields to prepopulate.
-   * @param {bool} [exclude=false]
-   *   A regular expression representing characters to exclude from
-   *   the target field.
-   * @param {string} [replace='']
-   *   A string to use as the replacement value for disallowed characters.
-   * @param {string} [suffix='']
-   *   A suffix to append at the end of the target field content.
-   */
   Drupal.viewsUi.FormFieldFiller = function ($target, exclude, replace, suffix) {
-
-    /**
-     *
-     * @type {jQuery}
-     */
     this.source = $('#edit-label');
 
-    /**
-     *
-     * @type {jQuery}
-     */
     this.target = $target;
 
-    /**
-     *
-     * @type {bool}
-     */
     this.exclude = exclude || false;
 
-    /**
-     *
-     * @type {string}
-     */
     this.replace = replace || '';
 
-    /**
-     *
-     * @type {string}
-     */
     this.suffix = suffix || '';
 
-    // Create bound versions of this instance's object methods to use as event
-    // handlers. This will let us easily unbind those specific handlers later
-    // on. NOTE: jQuery.proxy will not work for this because it assumes we want
-    // only one bound version of an object method, whereas we need one version
-    // per object instance.
     var self = this;
 
-    /**
-     * Populate the target form field with the altered source field value.
-     *
-     * @return {*}
-     *   The result of the _populate call, which should be undefined.
-     */
-    this.populate = function () { return self._populate.call(self); };
-
-    /**
-     * Stop prepopulating the form fields.
-     *
-     * @return {*}
-     *   The result of the _unbind call, which should be undefined.
-     */
-    this.unbind = function () { return self._unbind.call(self); };
+    this.populate = function () {
+      return self._populate.call(self);
+    };
+
+    this.unbind = function () {
+      return self._unbind.call(self);
+    };
 
     this.bind();
-    // Object constructor; no return value.
   };
 
-  $.extend(Drupal.viewsUi.FormFieldFiller.prototype, /** @lends Drupal.viewsUi.FormFieldFiller# */{
-
-    /**
-     * Bind the form-filling behavior.
-     */
-    bind: function () {
+  $.extend(Drupal.viewsUi.FormFieldFiller.prototype, {
+    bind: function bind() {
       this.unbind();
-      // Populate the form field when the source changes.
+
       this.source.on('keyup.viewsUi change.viewsUi', this.populate);
-      // Quit populating the field as soon as it gets focus.
+
       this.target.on('focus.viewsUi', this.unbind);
     },
 
-    /**
-     * Get the source form field value as altered by the passed-in parameters.
-     *
-     * @return {string}
-     *   The source form field value.
-     */
-    getTransliterated: function () {
+    getTransliterated: function getTransliterated() {
       var from = this.source.val();
       if (this.exclude) {
         from = from.toLowerCase().replace(this.exclude, this.replace);
@@ -195,112 +99,60 @@
       return from;
     },
 
-    /**
-     * Populate the target form field with the altered source field value.
-     */
-    _populate: function () {
+    _populate: function _populate() {
       var transliterated = this.getTransliterated();
       var suffix = this.suffix;
       this.target.each(function (i) {
-        // Ensure that the maxlength is not exceeded by prepopulating the field.
         var maxlength = $(this).attr('maxlength') - suffix.length;
         $(this).val(transliterated.substr(0, maxlength) + suffix);
       });
     },
 
-    /**
-     * Stop prepopulating the form fields.
-     */
-    _unbind: function () {
+    _unbind: function _unbind() {
       this.source.off('keyup.viewsUi change.viewsUi', this.populate);
       this.target.off('focus.viewsUi', this.unbind);
     },
 
-    /**
-     * Bind event handlers to new form fields, after they're replaced via Ajax.
-     *
-     * @param {jQuery} $fields
-     *   Fields to rebind functionality to.
-     */
-    rebind: function ($fields) {
+    rebind: function rebind($fields) {
       this.target = $fields;
       this.bind();
     }
   });
 
-  /**
-   * Adds functionality for the add item form.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches the functionality in {@link Drupal.viewsUi.AddItemForm} to the
-   *   forms in question.
-   */
   Drupal.behaviors.addItemForm = {
-    attach: function (context) {
+    attach: function attach(context) {
       var $context = $(context);
       var $form = $context;
-      // The add handler form may have an id of views-ui-add-handler-form--n.
+
       if (!$context.is('form[id^="views-ui-add-handler-form"]')) {
         $form = $context.find('form[id^="views-ui-add-handler-form"]');
       }
       if ($form.once('views-ui-add-handler-form').length) {
-        // If we we have an unprocessed views-ui-add-handler-form, let's
-        // instantiate.
         new Drupal.viewsUi.AddItemForm($form);
       }
     }
   };
 
-  /**
-   * Constructs a new AddItemForm.
-   *
-   * @constructor
-   *
-   * @param {jQuery} $form
-   *   The form element used.
-   */
   Drupal.viewsUi.AddItemForm = function ($form) {
-
-    /**
-     *
-     * @type {jQuery}
-     */
     this.$form = $form;
     this.$form.find('.views-filterable-options :checkbox').on('click', $.proxy(this.handleCheck, this));
 
-    /**
-     * Find the wrapper of the displayed text.
-     */
     this.$selected_div = this.$form.find('.views-selected-options').parent();
     this.$selected_div.hide();
 
-    /**
-     *
-     * @type {Array}
-     */
     this.checkedItems = [];
   };
 
-  /**
-   * Handles a checkbox check.
-   *
-   * @param {jQuery.Event} event
-   *   The event triggered.
-   */
   Drupal.viewsUi.AddItemForm.prototype.handleCheck = function (event) {
     var $target = $(event.target);
     var label = $.trim($target.closest('td').next().html());
-    // Add/remove the checked item to the list.
+
     if ($target.is(':checked')) {
       this.$selected_div.show().css('display', 'block');
       this.checkedItems.push(label);
-    }
-    else {
+    } else {
       var position = $.inArray(label, this.checkedItems);
-      // Delete the item from the list and make sure that the list doesn't have
-      // undefined items left.
+
       for (var i = 0; i < this.checkedItems.length; i++) {
         if (i === position) {
           this.checkedItems.splice(i, 1);
@@ -308,7 +160,7 @@
           break;
         }
       }
-      // Hide it again if none item is selected.
+
       if (this.checkedItems.length === 0) {
         this.$selected_div.hide();
       }
@@ -316,30 +168,12 @@
     this.refreshCheckedItems();
   };
 
-  /**
-   * Refresh the display of the checked items.
-   */
   Drupal.viewsUi.AddItemForm.prototype.refreshCheckedItems = function () {
-    // Perhaps we should precache the text div, too.
-    this.$selected_div.find('.views-selected-options')
-      .html(this.checkedItems.join(', '))
-      .trigger('dialogContentResize');
+    this.$selected_div.find('.views-selected-options').html(this.checkedItems.join(', ')).trigger('dialogContentResize');
   };
 
-  /**
-   * The input field items that add displays must be rendered as `<input>`
-   * elements. The following behavior detaches the `<input>` elements from the
-   * DOM, wraps them in an unordered list, then appends them to the list of
-   * tabs.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Fixes the input elements needed.
-   */
   Drupal.behaviors.viewsUiRenderAddViewButton = {
-    attach: function (context) {
-      // Build the add display menu and pull the display input buttons into it.
+    attach: function attach(context) {
       var $menu = $(context).find('#views-display-menu-tabs').once('views-ui-render-add-view-button');
       if (!$menu.length) {
         return;
@@ -347,11 +181,8 @@
 
       var $addDisplayDropdown = $('<li class="add"><a href="#"><span class="icon add"></span>' + Drupal.t('Add') + '</a><ul class="action-list" style="display:none;"></ul></li>');
       var $displayButtons = $menu.nextAll('input.add-display').detach();
-      $displayButtons.appendTo($addDisplayDropdown.find('.action-list')).wrap('<li>')
-        .parent().eq(0).addClass('first').end().eq(-1).addClass('last');
-      // Remove the 'Add ' prefix from the button labels since they're being
-      // placed in an 'Add' dropdown. @todo This assumes English, but so does
-      // $addDisplayDropdown above. Add support for translation.
+      $displayButtons.appendTo($addDisplayDropdown.find('.action-list')).wrap('<li>').parent().eq(0).addClass('first').end().eq(-1).addClass('last');
+
       $displayButtons.each(function () {
         var label = $(this).val();
         if (label.substr(0, 4) === 'Add ') {
@@ -360,19 +191,12 @@
       });
       $addDisplayDropdown.appendTo($menu);
 
-      // Add the click handler for the add display button.
       $menu.find('li.add > a').on('click', function (event) {
         event.preventDefault();
         var $trigger = $(this);
         Drupal.behaviors.viewsUiRenderAddViewButton.toggleMenu($trigger);
       });
-      // Add a mouseleave handler to close the dropdown when the user mouses
-      // away from the item. We use mouseleave instead of mouseout because
-      // the user is going to trigger mouseout when she moves from the trigger
-      // link to the sub menu items.
-      // We use the live binder because the open class on this item will be
-      // toggled on and off and we want the handler to take effect in the cases
-      // that the class is present, but not when it isn't.
+
       $('li.add', $menu).on('mouseleave', function (event) {
         var $this = $(this);
         var $trigger = $this.children('a[href="#"]');
@@ -383,67 +207,29 @@
     }
   };
 
-  /**
-   * Toggle menu visibility.
-   *
-   * @param {jQuery} $trigger
-   *   The element where the toggle was triggered.
-   *
-   *
-   * @note [@jessebeach] I feel like the following should be a more generic
-   *   function and not written specifically for this UI, but I'm not sure
-   *   where to put it.
-   */
   Drupal.behaviors.viewsUiRenderAddViewButton.toggleMenu = function ($trigger) {
     $trigger.parent().toggleClass('open');
     $trigger.next().slideToggle('fast');
   };
 
-  /**
-   * Add search options to the views ui.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches {@link Drupal.viewsUi.OptionsSearch} to the views ui filter
-   *   options.
-   */
   Drupal.behaviors.viewsUiSearchOptions = {
-    attach: function (context) {
+    attach: function attach(context) {
       var $context = $(context);
       var $form = $context;
-      // The add handler form may have an id of views-ui-add-handler-form--n.
+
       if (!$context.is('form[id^="views-ui-add-handler-form"]')) {
         $form = $context.find('form[id^="views-ui-add-handler-form"]');
       }
-      // Make sure we don't add more than one event handler to the same form.
+
       if ($form.once('views-ui-filter-options').length) {
         new Drupal.viewsUi.OptionsSearch($form);
       }
     }
   };
 
-  /**
-   * Constructor for the viewsUi.OptionsSearch object.
-   *
-   * The OptionsSearch object filters the available options on a form according
-   * to the user's search term. Typing in "taxonomy" will show only those
-   * options containing "taxonomy" in their label.
-   *
-   * @constructor
-   *
-   * @param {jQuery} $form
-   *   The form element.
-   */
   Drupal.viewsUi.OptionsSearch = function ($form) {
-
-    /**
-     *
-     * @type {jQuery}
-     */
     this.$form = $form;
 
-    // Click on the title checks the box.
     this.$form.on('click', 'td.title', function (event) {
       var $target = $(event.currentTarget);
       $target.closest('tr').find('input').trigger('click');
@@ -456,13 +242,8 @@
     this.$searchBox = this.$form.find(searchBoxSelector);
     this.$controlGroup = this.$form.find(controlGroupSelector);
 
-    /**
-     * Get a list of option labels and their corresponding divs and maintain it
-     * in memory, so we have as little overhead as possible at keyup time.
-     */
     this.options = this.getOptions(this.$form.find('.filterable-option'));
 
-    // Trap the ENTER key in the search box so that it doesn't submit the form.
     this.$searchBox.on('keypress', function (event) {
       if (event.which === 13) {
         event.preventDefault();
@@ -470,19 +251,8 @@
     });
   };
 
-  $.extend(Drupal.viewsUi.OptionsSearch.prototype, /** @lends Drupal.viewsUi.OptionsSearch# */{
-
-    /**
-     * Assemble a list of all the filterable options on the form.
-     *
-     * @param {jQuery} $allOptions
-     *   A jQuery object representing the rows of filterable options to be
-     *   shown and hidden depending on the user's search terms.
-     *
-     * @return {Array}
-     *   An array of all the filterable options.
-     */
-    getOptions: function ($allOptions) {
+  $.extend(Drupal.viewsUi.OptionsSearch.prototype, {
+    getOptions: function getOptions($allOptions) {
       var $title;
       var $description;
       var $option;
@@ -493,41 +263,27 @@
         $title = $option.find('.title');
         $description = $option.find('.description');
         options[i] = {
-          // Search on the lowercase version of the title text + description.
           searchText: $title.text().toLowerCase() + ' ' + $description.text().toLowerCase(),
-          // Maintain a reference to the jQuery object for each row, so we don't
-          // have to create a new object inside the performance-sensitive keyup
-          // handler.
+
           $div: $option
         };
       }
       return options;
     },
 
-    /**
-     * Filter handler for the search box and type select that hides or shows the relevant
-     * options.
-     *
-     * @param {jQuery.Event} event
-     *   The formUpdated event.
-     */
-    handleFilter: function (event) {
-      // Determine the user's search query. The search text has been converted
-      // to lowercase.
+    handleFilter: function handleFilter(event) {
       var search = this.$searchBox.val().toLowerCase();
       var words = search.split(' ');
-      // Get selected Group
+
       var group = this.$controlGroup.val();
 
-      // Search through the search texts in the form for matching text.
       this.options.forEach(function (option) {
         function hasWord(word) {
           return option.searchText.indexOf(word) !== -1;
         }
 
         var found = true;
-        // Each word in the search string has to match the item in order for the
-        // item to be shown.
+
         if (search) {
           found = words.every(hasWord);
         }
@@ -538,58 +294,32 @@
         option.$div.toggle(found);
       });
 
-      // Adapt dialog to content size.
       $(event.target).trigger('dialogContentResize');
     }
   });
 
-  /**
-   * Preview functionality in the views edit form.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches the preview functionality to the view edit form.
-   */
   Drupal.behaviors.viewsUiPreview = {
-    attach: function (context) {
-      // Only act on the edit view form.
+    attach: function attach(context) {
       var $contextualFiltersBucket = $(context).find('.views-display-column .views-ui-display-tab-bucket.argument');
       if ($contextualFiltersBucket.length === 0) {
         return;
       }
 
-      // If the display has no contextual filters, hide the form where you
-      // enter the contextual filters for the live preview. If it has contextual
-      // filters, show the form.
       var $contextualFilters = $contextualFiltersBucket.find('.views-display-setting a');
       if ($contextualFilters.length) {
         $('#preview-args').parent().show();
-      }
-      else {
+      } else {
         $('#preview-args').parent().hide();
       }
 
-      // Executes an initial preview.
       if ($('#edit-displays-live-preview').once('edit-displays-live-preview').is(':checked')) {
         $('#preview-submit').once('edit-displays-live-preview').trigger('click');
       }
     }
   };
 
-  /**
-   * Rearranges the filters.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attach handlers to make it possible to rearange the filters in the form
-   *   in question.
-   *   @see Drupal.viewsUi.RearrangeFilterHandler
-   */
   Drupal.behaviors.viewsUiRearrangeFilter = {
-    attach: function (context) {
-      // Only act on the rearrange filter form.
+    attach: function attach(context) {
       if (typeof Drupal.tableDrag === 'undefined' || typeof Drupal.tableDrag['views-rearrange-filters'] === 'undefined') {
         return;
       }
@@ -602,162 +332,58 @@
     }
   };
 
-  /**
-   * Improve the UI of the rearrange filters dialog box.
-   *
-   * @constructor
-   *
-   * @param {jQuery} $table
-   *   The table in the filter form.
-   * @param {jQuery} $operator
-   *   The filter groups operator element.
-   */
   Drupal.viewsUi.RearrangeFilterHandler = function ($table, $operator) {
-
-    /**
-     * Keep a reference to the `<table>` being altered and to the div containing
-     * the filter groups operator dropdown (if it exists).
-     */
     this.table = $table;
 
-    /**
-     *
-     * @type {jQuery}
-     */
     this.operator = $operator;
 
-    /**
-     *
-     * @type {bool}
-     */
     this.hasGroupOperator = this.operator.length > 0;
 
-    /**
-     * Keep a reference to all draggable rows within the table.
-     *
-     * @type {jQuery}
-     */
     this.draggableRows = $table.find('.draggable');
 
-    /**
-     * Keep a reference to the buttons for adding and removing filter groups.
-     *
-     * @type {jQuery}
-     */
     this.addGroupButton = $('input#views-add-group');
 
-    /**
-     * @type {jQuery}
-     */
     this.removeGroupButtons = $table.find('input.views-remove-group');
 
-    // Add links that duplicate the functionality of the (hidden) add and remove
-    // buttons.
     this.insertAddRemoveFilterGroupLinks();
 
-    // When there is a filter groups operator dropdown on the page, create
-    // duplicates of the dropdown between each pair of filter groups.
     if (this.hasGroupOperator) {
-
-      /**
-       * @type {jQuery}
-       */
       this.dropdowns = this.duplicateGroupsOperator();
       this.syncGroupsOperators();
     }
 
-    // Add methods to the tableDrag instance to account for operator cells
-    // (which span multiple rows), the operator labels next to each filter
-    // (e.g., "And" or "Or"), the filter groups, and other special aspects of
-    // this tableDrag instance.
     this.modifyTableDrag();
 
-    // Initialize the operator labels (e.g., "And" or "Or") that are displayed
-    // next to the filters in each group, and bind a handler so that they change
-    // based on the values of the operator dropdown within that group.
     this.redrawOperatorLabels();
-    $table.find('.views-group-title select')
-      .once('views-rearrange-filter-handler')
-      .on('change.views-rearrange-filter-handler', $.proxy(this, 'redrawOperatorLabels'));
-
-    // Bind handlers so that when a "Remove" link is clicked, we:
-    // - Update the rowspans of cells containing an operator dropdown (since
-    //   they need to change to reflect the number of rows in each group).
-    // - Redraw the operator labels next to the filters in the group (since the
-    //   filter that is currently displayed last in each group is not supposed
-    //   to have a label display next to it).
-    $table.find('a.views-groups-remove-link')
-      .once('views-rearrange-filter-handler')
-      .on('click.views-rearrange-filter-handler', $.proxy(this, 'updateRowspans'))
-      .on('click.views-rearrange-filter-handler', $.proxy(this, 'redrawOperatorLabels'));
+    $table.find('.views-group-title select').once('views-rearrange-filter-handler').on('change.views-rearrange-filter-handler', $.proxy(this, 'redrawOperatorLabels'));
+
+    $table.find('a.views-groups-remove-link').once('views-rearrange-filter-handler').on('click.views-rearrange-filter-handler', $.proxy(this, 'updateRowspans')).on('click.views-rearrange-filter-handler', $.proxy(this, 'redrawOperatorLabels'));
   };
 
-  $.extend(Drupal.viewsUi.RearrangeFilterHandler.prototype, /** @lends Drupal.viewsUi.RearrangeFilterHandler# */{
-
-    /**
-     * Insert links that allow filter groups to be added and removed.
-     */
-    insertAddRemoveFilterGroupLinks: function () {
-
-      // Insert a link for adding a new group at the top of the page, and make
-      // it match the action link styling used in a typical page.html.twig.
-      // Since Drupal does not provide a theme function for this markup this is
-      // the best we can do.
-      $('<ul class="action-links"><li><a id="views-add-group-link" href="#">' + this.addGroupButton.val() + '</a></li></ul>')
-        .prependTo(this.table.parent())
-        // When the link is clicked, dynamically click the hidden form button
-        // for adding a new filter group.
-        .once('views-rearrange-filter-handler')
-        .find('#views-add-group-link')
-        .on('click.views-rearrange-filter-handler', $.proxy(this, 'clickAddGroupButton'));
-
-      // Find each (visually hidden) button for removing a filter group and
-      // insert a link next to it.
+  $.extend(Drupal.viewsUi.RearrangeFilterHandler.prototype, {
+    insertAddRemoveFilterGroupLinks: function insertAddRemoveFilterGroupLinks() {
+      $('<ul class="action-links"><li><a id="views-add-group-link" href="#">' + this.addGroupButton.val() + '</a></li></ul>').prependTo(this.table.parent()).once('views-rearrange-filter-handler').find('#views-add-group-link').on('click.views-rearrange-filter-handler', $.proxy(this, 'clickAddGroupButton'));
+
       var length = this.removeGroupButtons.length;
       var i;
       for (i = 0; i < length; i++) {
         var $removeGroupButton = $(this.removeGroupButtons[i]);
         var buttonId = $removeGroupButton.attr('id');
-        $('<a href="#" class="views-remove-group-link">' + Drupal.t('Remove group') + '</a>')
-          .insertBefore($removeGroupButton)
-          // When the link is clicked, dynamically click the corresponding form
-          // button.
-          .once('views-rearrange-filter-handler')
-          .on('click.views-rearrange-filter-handler', {buttonId: buttonId}, $.proxy(this, 'clickRemoveGroupButton'));
+        $('<a href="#" class="views-remove-group-link">' + Drupal.t('Remove group') + '</a>').insertBefore($removeGroupButton).once('views-rearrange-filter-handler').on('click.views-rearrange-filter-handler', { buttonId: buttonId }, $.proxy(this, 'clickRemoveGroupButton'));
       }
     },
 
-    /**
-     * Dynamically click the button that adds a new filter group.
-     *
-     * @param {jQuery.Event} event
-     *   The event triggered.
-     */
-    clickAddGroupButton: function (event) {
+    clickAddGroupButton: function clickAddGroupButton(event) {
       this.addGroupButton.trigger('mousedown');
       event.preventDefault();
     },
 
-    /**
-     * Dynamically click a button for removing a filter group.
-     *
-     * @param {jQuery.Event} event
-     *   Event being triggered, with event.data.buttonId set to the ID of the
-     *   form button that should be clicked.
-     */
-    clickRemoveGroupButton: function (event) {
+    clickRemoveGroupButton: function clickRemoveGroupButton(event) {
       this.table.find('#' + event.data.buttonId).trigger('mousedown');
       event.preventDefault();
     },
 
-    /**
-     * Move the groups operator so that it's between the first two groups, and
-     * duplicate it between any subsequent groups.
-     *
-     * @return {jQuery}
-     *   An operator element.
-     */
-    duplicateGroupsOperator: function () {
+    duplicateGroupsOperator: function duplicateGroupsOperator() {
       var dropdowns;
       var newRow;
       var titleRow;
@@ -768,26 +394,20 @@
         return this.operator;
       }
 
-      // Get rid of the explanatory text around the operator; its placement is
-      // explanatory enough.
       this.operator.find('label').add('div.description').addClass('visually-hidden');
       this.operator.find('select').addClass('form-select');
 
-      // Keep a list of the operator dropdowns, so we can sync their behavior
-      // later.
       dropdowns = this.operator;
 
-      // Move the operator to a new row just above the second group.
       titleRow = $('tr#views-group-title-2');
       newRow = $('<tr class="filter-group-operator-row"><td colspan="5"></td></tr>');
       newRow.find('td').append(this.operator);
       newRow.insertBefore(titleRow);
       var length = titleRows.length;
-      // Starting with the third group, copy the operator to a new row above the
-      // group title.
+
       for (var i = 2; i < length; i++) {
         titleRow = $(titleRows[i]);
-        // Make a copy of the operator dropdown and put it in a new table row.
+
         var fakeOperator = this.operator.clone();
         fakeOperator.attr('id', '');
         newRow = $('<tr class="filter-group-operator-row"><td colspan="5"></td></tr>');
@@ -799,63 +419,30 @@
       return dropdowns;
     },
 
-    /**
-     * Make the duplicated groups operators change in sync with each other.
-     */
-    syncGroupsOperators: function () {
+    syncGroupsOperators: function syncGroupsOperators() {
       if (this.dropdowns.length < 2) {
-        // We only have one dropdown (or none at all), so there's nothing to
-        // sync.
         return;
       }
 
       this.dropdowns.on('change', $.proxy(this, 'operatorChangeHandler'));
     },
 
-    /**
-     * Click handler for the operators that appear between filter groups.
-     *
-     * Forces all operator dropdowns to have the same value.
-     *
-     * @param {jQuery.Event} event
-     *   The event triggered.
-     */
-    operatorChangeHandler: function (event) {
+    operatorChangeHandler: function operatorChangeHandler(event) {
       var $target = $(event.target);
       var operators = this.dropdowns.find('select').not($target);
 
-      // Change the other operators to match this new value.
       operators.val($target.val());
     },
 
-    /**
-     * @method
-     */
-    modifyTableDrag: function () {
+    modifyTableDrag: function modifyTableDrag() {
       var tableDrag = Drupal.tableDrag['views-rearrange-filters'];
       var filterHandler = this;
 
-      /**
-       * Override the row.onSwap method from tabledrag.js.
-       *
-       * When a row is dragged to another place in the table, several things
-       * need to occur.
-       * - The row needs to be moved so that it's within one of the filter
-       * groups.
-       * - The operator cells that span multiple rows need their rowspan
-       * attributes updated to reflect the number of rows in each group.
-       * - The operator labels that are displayed next to each filter need to
-       * be redrawn, to account for the row's new location.
-       */
       tableDrag.row.prototype.onSwap = function () {
         if (filterHandler.hasGroupOperator) {
-          // Make sure the row that just got moved (this.group) is inside one
-          // of the filter groups (i.e. below an empty marker row or a
-          // draggable). If it isn't, move it down one.
           var thisRow = $(this.group);
           var previousRow = thisRow.prev('tr');
           if (previousRow.length && !previousRow.hasClass('group-message') && !previousRow.hasClass('draggable')) {
-            // Move the dragged row down one.
             var next = thisRow.next();
             if (next.is('tr')) {
               this.swap('after', next);
@@ -863,31 +450,19 @@
           }
           filterHandler.updateRowspans();
         }
-        // Redraw the operator labels that are displayed next to each filter, to
-        // account for the row's new location.
+
         filterHandler.redrawOperatorLabels();
       };
 
-      /**
-       * Override the onDrop method from tabledrag.js.
-       */
       tableDrag.onDrop = function () {
-        // If the tabledrag change marker (i.e., the "*") has been inserted
-        // inside a row after the operator label (i.e., "And" or "Or")
-        // rearrange the items so the operator label continues to appear last.
         var changeMarker = $(this.oldRowElement).find('.tabledrag-changed');
         if (changeMarker.length) {
-          // Search for occurrences of the operator label before the change
-          // marker, and reverse them.
           var operatorLabel = changeMarker.prevAll('.views-operator-label');
           if (operatorLabel.length) {
             operatorLabel.insertAfter(changeMarker);
           }
         }
 
-        // Make sure the "group" dropdown is properly updated when rows are
-        // dragged into an empty filter group. This is borrowed heavily from
-        // the block.js implementation of tableDrag.onDrop().
         var groupRow = $(this.rowObject.element).prevAll('tr.group-message').get(0);
         var groupName = groupRow.className.replace(/([^ ]+[ ]+)*group-([^ ]+)-message([ ]+[^ ]+)*/, '$2');
         var groupField = $('select.views-group-select', this.rowObject.element);
@@ -899,57 +474,30 @@
       };
     },
 
-    /**
-     * Redraw the operator labels that are displayed next to each filter.
-     */
-    redrawOperatorLabels: function () {
+    redrawOperatorLabels: function redrawOperatorLabels() {
       for (var i = 0; i < this.draggableRows.length; i++) {
-        // Within the row, the operator labels are displayed inside the first
-        // table cell (next to the filter name).
         var $draggableRow = $(this.draggableRows[i]);
         var $firstCell = $draggableRow.find('td').eq(0);
         if ($firstCell.length) {
-          // The value of the operator label ("And" or "Or") is taken from the
-          // first operator dropdown we encounter, going backwards from the
-          // current row. This dropdown is the one associated with the current
-          // row's filter group.
           var operatorValue = $draggableRow.prevAll('.views-group-title').find('option:selected').html();
           var operatorLabel = '<span class="views-operator-label">' + operatorValue + '</span>';
-          // If the next visible row after this one is a draggable filter row,
-          // display the operator label next to the current row. (Checking for
-          // visibility is necessary here since the "Remove" links hide the
-          // removed row but don't actually remove it from the document).
+
           var $nextRow = $draggableRow.nextAll(':visible').eq(0);
           var $existingOperatorLabel = $firstCell.find('.views-operator-label');
           if ($nextRow.hasClass('draggable')) {
-            // If an operator label was already there, replace it with the new
-            // one.
             if ($existingOperatorLabel.length) {
               $existingOperatorLabel.replaceWith(operatorLabel);
+            } else {
+                $firstCell.append(operatorLabel);
+              }
+          } else {
+              $existingOperatorLabel.remove();
             }
-            // Otherwise, append the operator label to the end of the table
-            // cell.
-            else {
-              $firstCell.append(operatorLabel);
-            }
-          }
-          // If the next row doesn't contain a filter, then this is the last row
-          // in the group. We don't want to display the operator there (since
-          // operators should only display between two related filters, e.g.
-          // "filter1 AND filter2 AND filter3"). So we remove any existing label
-          // that this row has.
-          else {
-            $existingOperatorLabel.remove();
-          }
         }
       }
     },
 
-    /**
-     * Update the rowspan attribute of each cell containing an operator
-     * dropdown.
-     */
-    updateRowspans: function () {
+    updateRowspans: function updateRowspans() {
       var $row;
       var $currentEmptyRow;
       var draggableCount;
@@ -959,39 +507,25 @@
       for (var i = 0; i < length; i++) {
         $row = $(rows[i]);
         if ($row.hasClass('views-group-title')) {
-          // This row is a title row.
-          // Keep a reference to the cell containing the dropdown operator.
           $operatorCell = $row.find('td.group-operator');
-          // Assume this filter group is empty, until we find otherwise.
+
           draggableCount = 0;
           $currentEmptyRow = $row.next('tr');
           $currentEmptyRow.removeClass('group-populated').addClass('group-empty');
-          // The cell with the dropdown operator should span the title row and
-          // the "this group is empty" row.
+
           $operatorCell.attr('rowspan', 2);
-        }
-        else if ($row.hasClass('draggable') && $row.is(':visible')) {
-          // We've found a visible filter row, so we now know the group isn't
-          // empty.
+        } else if ($row.hasClass('draggable') && $row.is(':visible')) {
           draggableCount++;
           $currentEmptyRow.removeClass('group-empty').addClass('group-populated');
-          // The operator cell should span all draggable rows, plus the title.
+
           $operatorCell.attr('rowspan', draggableCount + 1);
         }
       }
     }
   });
 
-  /**
-   * Add a select all checkbox, which checks each checkbox at once.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches select all functionality to the views filter form.
-   */
   Drupal.behaviors.viewsFilterConfigSelectAll = {
-    attach: function (context) {
+    attach: function attach(context) {
       var $context = $(context);
 
       var $selectAll = $context.find('.js-form-item-options-value-all').once('filterConfigSelectAll');
@@ -999,14 +533,11 @@
       var $checkboxes = $selectAll.closest('.form-checkboxes').find('.js-form-type-checkbox:not(.js-form-item-options-value-all) input[type="checkbox"]');
 
       if ($selectAll.length) {
-         // Show the select all checkbox.
         $selectAll.show();
         $selectAllCheckbox.on('click', function () {
-          // Update all checkbox beside the select all checkbox.
           $checkboxes.prop('checked', $(this).is(':checked'));
         });
 
-        // Uncheck the select all checkbox if any of the others are unchecked.
         $checkboxes.on('click', function () {
           if ($(this).is('checked') === false) {
             $selectAllCheckbox.prop('checked', false);
@@ -1016,30 +547,14 @@
     }
   };
 
-  /**
-   * Remove icon class from elements that are themed as buttons or dropbuttons.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Removes the icon class from certain views elements.
-   */
   Drupal.behaviors.viewsRemoveIconClass = {
-    attach: function (context) {
+    attach: function attach(context) {
       $(context).find('.dropbutton').once('dropbutton-icon').find('.icon').removeClass('icon');
     }
   };
 
-  /**
-   * Change "Expose filter" buttons into checkboxes.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Changes buttons into checkboxes via {@link Drupal.viewsUi.Checkboxifier}.
-   */
   Drupal.behaviors.viewsUiCheckboxify = {
-    attach: function (context, settings) {
+    attach: function attach(context, settings) {
       var $buttons = $('[data-drupal-selector="edit-options-expose-button-button"], [data-drupal-selector="edit-options-group-button-button"]').once('views-ui-checkboxify');
       var length = $buttons.length;
       var i;
@@ -1049,17 +564,8 @@
     }
   };
 
-  /**
-   * Change the default widget to select the default group according to the
-   * selected widget for the exposed group.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Changes the default widget based on user input.
-   */
   Drupal.behaviors.viewsUiChangeDefaultWidget = {
-    attach: function (context) {
+    attach: function attach(context) {
       var $context = $(context);
 
       function changeDefaultWidget(event) {
@@ -1067,110 +573,64 @@
           $context.find('input.default-radios').parent().hide();
           $context.find('td.any-default-radios-row').parent().hide();
           $context.find('input.default-checkboxes').parent().show();
-        }
-        else {
+        } else {
           $context.find('input.default-checkboxes').parent().hide();
           $context.find('td.any-default-radios-row').parent().show();
           $context.find('input.default-radios').parent().show();
         }
       }
 
-      // Update on widget change.
-      $context.find('input[name="options[group_info][multiple]"]')
-        .on('change', changeDefaultWidget)
-        // Update the first time the form is rendered.
-        .trigger('change');
+      $context.find('input[name="options[group_info][multiple]"]').on('change', changeDefaultWidget).trigger('change');
     }
   };
 
-  /**
-   * Attaches expose filter button to a checkbox that triggers its click event.
-   *
-   * @constructor
-   *
-   * @param {HTMLElement} button
-   *   The DOM object representing the button to be checkboxified.
-   */
   Drupal.viewsUi.Checkboxifier = function (button) {
     this.$button = $(button);
     this.$parent = this.$button.parent('div.views-expose, div.views-grouped');
     this.$input = this.$parent.find('input:checkbox, input:radio');
-    // Hide the button and its description.
+
     this.$button.hide();
     this.$parent.find('.exposed-description, .grouped-description').hide();
 
     this.$input.on('click', $.proxy(this, 'clickHandler'));
-
   };
 
-  /**
-   * When the checkbox is checked or unchecked, simulate a button press.
-   *
-   * @param {jQuery.Event} e
-   *   The event triggered.
-   */
   Drupal.viewsUi.Checkboxifier.prototype.clickHandler = function (e) {
-    this.$button
-      .trigger('click')
-      .trigger('submit');
+    this.$button.trigger('click').trigger('submit');
   };
 
-  /**
-   * Change the Apply button text based upon the override select state.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches behavior to change the Apply button according to the current
-   *   state.
-   */
   Drupal.behaviors.viewsUiOverrideSelect = {
-    attach: function (context) {
+    attach: function attach(context) {
       $(context).find('[data-drupal-selector="edit-override-dropdown"]').once('views-ui-override-button-text').each(function () {
-        // Closures! :(
         var $context = $(context);
         var $submit = $context.find('[id^=edit-submit]');
         var old_value = $submit.val();
 
-        $submit.once('views-ui-override-button-text')
-          .on('mouseup', function () {
-            $(this).val(old_value);
-            return true;
-          });
+        $submit.once('views-ui-override-button-text').on('mouseup', function () {
+          $(this).val(old_value);
+          return true;
+        });
 
         $(this).on('change', function () {
           var $this = $(this);
           if ($this.val() === 'default') {
             $submit.val(Drupal.t('Apply (all displays)'));
-          }
-          else if ($this.val() === 'default_revert') {
+          } else if ($this.val() === 'default_revert') {
             $submit.val(Drupal.t('Revert to default'));
-          }
-          else {
+          } else {
             $submit.val(Drupal.t('Apply (this display)'));
           }
           var $dialog = $context.closest('.ui-dialog-content');
           $dialog.trigger('dialogButtonsChange');
-        })
-          .trigger('change');
+        }).trigger('change');
       });
-
     }
   };
 
-  /**
-   * Functionality for the remove link in the views UI.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches behavior for the remove view and remove display links.
-   */
   Drupal.behaviors.viewsUiHandlerRemoveLink = {
-    attach: function (context) {
+    attach: function attach(context) {
       var $context = $(context);
-      // Handle handler deletion by looking for the hidden checkbox and hiding
-      // the row.
+
       $context.find('a.views-remove-link').once('views').on('click', function (event) {
         var id = $(this).attr('id').replace('views-remove-link-', '');
         $context.find('#views-row-' + id).hide();
@@ -1178,8 +638,6 @@
         event.preventDefault();
       });
 
-      // Handle display deletion by looking for the hidden checkbox and hiding
-      // the row.
       $context.find('a.display-remove-link').once('display').on('click', function (event) {
         var id = $(this).attr('id').replace('display-remove-link-', '');
         $context.find('#display-row-' + id).hide();
@@ -1188,5 +646,4 @@
       });
     }
   };
-
-})(jQuery, Drupal, drupalSettings);
+})(jQuery, Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/modules/views_ui/js/views_ui.listing.es6.js b/core/modules/views_ui/js/views_ui.listing.es6.js
new file mode 100644
index 000000000000..7d19cd498d2e
--- /dev/null
+++ b/core/modules/views_ui/js/views_ui.listing.es6.js
@@ -0,0 +1,54 @@
+/**
+ * @file
+ * Views listing behaviors.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * Filters the view listing tables by a text input search string.
+   *
+   * Text search input: input.views-filter-text
+   * Target table:      input.views-filter-text[data-table]
+   * Source text:       [data-drupal-selector="views-table-filter-text-source"]
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches the filter functionality to the views admin text search field.
+   */
+  Drupal.behaviors.viewTableFilterByText = {
+    attach: function (context, settings) {
+      var $input = $('input.views-filter-text').once('views-filter-text');
+      var $table = $($input.attr('data-table'));
+      var $rows;
+
+      function filterViewList(e) {
+        var query = $(e.target).val().toLowerCase();
+
+        function showViewRow(index, row) {
+          var $row = $(row);
+          var $sources = $row.find('[data-drupal-selector="views-table-filter-text-source"]');
+          var textMatch = $sources.text().toLowerCase().indexOf(query) !== -1;
+          $row.closest('tr').toggle(textMatch);
+        }
+
+        // Filter if the length of the query is at least 2 characters.
+        if (query.length >= 2) {
+          $rows.each(showViewRow);
+        }
+        else {
+          $rows.show();
+        }
+      }
+
+      if ($table.length) {
+        $rows = $table.find('tbody tr');
+        $input.on('keyup', filterViewList);
+      }
+    }
+  };
+
+}(jQuery, Drupal));
diff --git a/core/modules/views_ui/js/views_ui.listing.js b/core/modules/views_ui/js/views_ui.listing.js
index 7d19cd498d2e..7c998bf01254 100644
--- a/core/modules/views_ui/js/views_ui.listing.js
+++ b/core/modules/views_ui/js/views_ui.listing.js
@@ -1,26 +1,17 @@
 /**
- * @file
- * Views listing behaviors.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./modules/views_ui/js/views_ui.listing.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   * Filters the view listing tables by a text input search string.
-   *
-   * Text search input: input.views-filter-text
-   * Target table:      input.views-filter-text[data-table]
-   * Source text:       [data-drupal-selector="views-table-filter-text-source"]
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches the filter functionality to the views admin text search field.
-   */
   Drupal.behaviors.viewTableFilterByText = {
-    attach: function (context, settings) {
+    attach: function attach(context, settings) {
       var $input = $('input.views-filter-text').once('views-filter-text');
       var $table = $($input.attr('data-table'));
       var $rows;
@@ -35,11 +26,9 @@
           $row.closest('tr').toggle(textMatch);
         }
 
-        // Filter if the length of the query is at least 2 characters.
         if (query.length >= 2) {
           $rows.each(showViewRow);
-        }
-        else {
+        } else {
           $rows.show();
         }
       }
@@ -50,5 +39,4 @@
       }
     }
   };
-
-}(jQuery, Drupal));
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/package.json b/core/package.json
index 533386ace57c..bb9fee002f56 100644
--- a/core/package.json
+++ b/core/package.json
@@ -12,6 +12,7 @@
   },
   "devDependencies": {
     "babel-core": "6.24.1",
+    "babel-plugin-add-header-comment": "1.0.3",
     "babel-preset-env": "1.4.0",
     "chokidar": "1.6.1",
     "cross-env": "^4.0.0",
diff --git a/core/scripts/js/changeOrAdded.js b/core/scripts/js/changeOrAdded.js
index 1f8b1ee128f7..e6d1a96243ca 100644
--- a/core/scripts/js/changeOrAdded.js
+++ b/core/scripts/js/changeOrAdded.js
@@ -12,7 +12,14 @@ module.exports = (filePath) => {
     filePath,
     {
       sourceMaps: process.env.NODE_ENV === 'development' ? 'inline' : false,
-      comments: false
+      comments: false,
+      plugins: [
+        ['add-header-comment', {
+          'header': [
+            `DO NOT EDIT THIS FILE.\nAll changes should be applied to ${filePath}\nSee the following change record for more information,\nhttps://www.drupal.org/node/2873849\n@preserve`
+          ]
+        }]
+      ]
     },
     (err, result) => {
       if (err) {
diff --git a/core/scripts/js/rename-js-files-to-es6.sh b/core/scripts/js/rename-js-files-to-es6.sh
deleted file mode 100644
index 5ae2a890122f..000000000000
--- a/core/scripts/js/rename-js-files-to-es6.sh
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/bin/bash
-
-# Rename *.js files in *.es6.js. Only need to be run once.
-# Should be removed after *.es6.js files are committed to core.
-#
-# @internal This file is part of the core javascript build process and is only
-# meant to be used in that context.
-
-for js in `find ./{misc,modules,themes} -name '*.js'`;
-do
-   mv ${js} ${js%???}.es6.js;
-done
diff --git a/core/themes/bartik/color/preview.es6.js b/core/themes/bartik/color/preview.es6.js
new file mode 100644
index 000000000000..da8e0ca4e0b9
--- /dev/null
+++ b/core/themes/bartik/color/preview.es6.js
@@ -0,0 +1,49 @@
+/**
+ * @file
+ * Preview for the Bartik theme.
+ */
+(function ($, Drupal, drupalSettings) {
+
+  'use strict';
+
+  Drupal.color = {
+    logoChanged: false,
+    callback: function (context, settings, $form) {
+      // Change the logo to be the real one.
+      if (!this.logoChanged) {
+        $('.color-preview .color-preview-logo img').attr('src', drupalSettings.color.logo);
+        this.logoChanged = true;
+      }
+      // Remove the logo if the setting is toggled off.
+      if (drupalSettings.color.logo === null) {
+        $('div').remove('.color-preview-logo');
+      }
+
+      var $colorPreview = $form.find('.color-preview');
+      var $colorPalette = $form.find('.js-color-palette');
+
+      // Solid background.
+      $colorPreview.css('backgroundColor', $colorPalette.find('input[name="palette[bg]"]').val());
+
+      // Text preview.
+      $colorPreview.find('.color-preview-main h2, .color-preview .preview-content').css('color', $colorPalette.find('input[name="palette[text]"]').val());
+      $colorPreview.find('.color-preview-content a').css('color', $colorPalette.find('input[name="palette[link]"]').val());
+
+      // Sidebar block.
+      var $colorPreviewBlock = $colorPreview.find('.color-preview-sidebar .color-preview-block');
+      $colorPreviewBlock.css('background-color', $colorPalette.find('input[name="palette[sidebar]"]').val());
+      $colorPreviewBlock.css('border-color', $colorPalette.find('input[name="palette[sidebarborders]"]').val());
+
+      // Footer wrapper background.
+      $colorPreview.find('.color-preview-footer-wrapper').css('background-color', $colorPalette.find('input[name="palette[footer]"]').val());
+
+      // CSS3 Gradients.
+      var gradient_start = $colorPalette.find('input[name="palette[top]"]').val();
+      var gradient_end = $colorPalette.find('input[name="palette[bottom]"]').val();
+
+      $colorPreview.find('.color-preview-header').attr('style', 'background-color: ' + gradient_start + '; background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(' + gradient_start + '), to(' + gradient_end + ')); background-image: -moz-linear-gradient(-90deg, ' + gradient_start + ', ' + gradient_end + ');');
+
+      $colorPreview.find('.color-preview-site-name').css('color', $colorPalette.find('input[name="palette[titleslogan]"]').val());
+    }
+  };
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/themes/bartik/color/preview.js b/core/themes/bartik/color/preview.js
index da8e0ca4e0b9..ad34b2bc77e0 100644
--- a/core/themes/bartik/color/preview.js
+++ b/core/themes/bartik/color/preview.js
@@ -1,20 +1,23 @@
 /**
- * @file
- * Preview for the Bartik theme.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./themes/bartik/color/preview.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
+
 (function ($, Drupal, drupalSettings) {
 
   'use strict';
 
   Drupal.color = {
     logoChanged: false,
-    callback: function (context, settings, $form) {
-      // Change the logo to be the real one.
+    callback: function callback(context, settings, $form) {
       if (!this.logoChanged) {
         $('.color-preview .color-preview-logo img').attr('src', drupalSettings.color.logo);
         this.logoChanged = true;
       }
-      // Remove the logo if the setting is toggled off.
+
       if (drupalSettings.color.logo === null) {
         $('div').remove('.color-preview-logo');
       }
@@ -22,22 +25,17 @@
       var $colorPreview = $form.find('.color-preview');
       var $colorPalette = $form.find('.js-color-palette');
 
-      // Solid background.
       $colorPreview.css('backgroundColor', $colorPalette.find('input[name="palette[bg]"]').val());
 
-      // Text preview.
       $colorPreview.find('.color-preview-main h2, .color-preview .preview-content').css('color', $colorPalette.find('input[name="palette[text]"]').val());
       $colorPreview.find('.color-preview-content a').css('color', $colorPalette.find('input[name="palette[link]"]').val());
 
-      // Sidebar block.
       var $colorPreviewBlock = $colorPreview.find('.color-preview-sidebar .color-preview-block');
       $colorPreviewBlock.css('background-color', $colorPalette.find('input[name="palette[sidebar]"]').val());
       $colorPreviewBlock.css('border-color', $colorPalette.find('input[name="palette[sidebarborders]"]').val());
 
-      // Footer wrapper background.
       $colorPreview.find('.color-preview-footer-wrapper').css('background-color', $colorPalette.find('input[name="palette[footer]"]').val());
 
-      // CSS3 Gradients.
       var gradient_start = $colorPalette.find('input[name="palette[top]"]').val();
       var gradient_end = $colorPalette.find('input[name="palette[bottom]"]').val();
 
@@ -46,4 +44,4 @@
       $colorPreview.find('.color-preview-site-name').css('color', $colorPalette.find('input[name="palette[titleslogan]"]').val());
     }
   };
-})(jQuery, Drupal, drupalSettings);
+})(jQuery, Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/themes/seven/js/mobile.install.es6.js b/core/themes/seven/js/mobile.install.es6.js
new file mode 100644
index 000000000000..e7a0b5c18ac9
--- /dev/null
+++ b/core/themes/seven/js/mobile.install.es6.js
@@ -0,0 +1,33 @@
+(function () {
+
+  'use strict';
+
+  function findActiveStep(steps) {
+    for (var i = 0; i < steps.length; i++) {
+      if (steps[i].className === 'is-active') {
+        return i + 1;
+      }
+    }
+    // The final "Finished" step is never "active".
+    if (steps[steps.length - 1].className === 'done') {
+      return steps.length;
+    }
+    return 0;
+  }
+
+  function installStepsSetup() {
+    var steps = document.querySelectorAll('.task-list li');
+    if (steps.length) {
+      var header = document.querySelector('header[role="banner"]');
+      var stepIndicator = document.createElement('div');
+      stepIndicator.className = 'step-indicator';
+      stepIndicator.innerHTML = findActiveStep(steps) + '/' + steps.length;
+      header.appendChild(stepIndicator);
+    }
+  }
+
+  if (document.addEventListener) {
+    document.addEventListener('DOMContentLoaded', installStepsSetup);
+  }
+
+})();
diff --git a/core/themes/seven/js/mobile.install.js b/core/themes/seven/js/mobile.install.js
index e7a0b5c18ac9..a10f1b8cb22c 100644
--- a/core/themes/seven/js/mobile.install.js
+++ b/core/themes/seven/js/mobile.install.js
@@ -1,3 +1,11 @@
+/**
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./themes/seven/js/mobile.install.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
+
 (function () {
 
   'use strict';
@@ -8,7 +16,7 @@
         return i + 1;
       }
     }
-    // The final "Finished" step is never "active".
+
     if (steps[steps.length - 1].className === 'done') {
       return steps.length;
     }
@@ -29,5 +37,4 @@
   if (document.addEventListener) {
     document.addEventListener('DOMContentLoaded', installStepsSetup);
   }
-
-})();
+})();
\ No newline at end of file
diff --git a/core/themes/seven/js/nav-tabs.es6.js b/core/themes/seven/js/nav-tabs.es6.js
new file mode 100644
index 000000000000..ba44ffb944c9
--- /dev/null
+++ b/core/themes/seven/js/nav-tabs.es6.js
@@ -0,0 +1,55 @@
+/**
+ * @file
+ * Responsive navigation tabs.
+ *
+ * This also supports collapsible navigable is the 'is-collapsible' class is
+ * added to the main element, and a target element is included.
+ */
+(function ($, Drupal) {
+
+  'use strict';
+
+  function init(i, tab) {
+    var $tab = $(tab);
+    var $target = $tab.find('[data-drupal-nav-tabs-target]');
+    var isCollapsible = $tab.hasClass('is-collapsible');
+
+    function openMenu(e) {
+      $target.toggleClass('is-open');
+    }
+
+    function handleResize(e) {
+      $tab.addClass('is-horizontal');
+      var $tabs = $tab.find('.tabs');
+      var isHorizontal = $tabs.outerHeight() <= $tabs.find('.tabs__tab').outerHeight();
+      $tab.toggleClass('is-horizontal', isHorizontal);
+      if (isCollapsible) {
+        $tab.toggleClass('is-collapse-enabled', !isHorizontal);
+      }
+      if (isHorizontal) {
+        $target.removeClass('is-open');
+      }
+    }
+
+    $tab.addClass('position-container is-horizontal-enabled');
+
+    $tab.on('click.tabs', '[data-drupal-nav-tabs-trigger]', openMenu);
+    $(window).on('resize.tabs', Drupal.debounce(handleResize, 150)).trigger('resize.tabs');
+  }
+
+  /**
+   * Initialise the tabs JS.
+   */
+  Drupal.behaviors.navTabs = {
+    attach: function (context, settings) {
+      var $tabs = $(context).find('[data-drupal-nav-tabs]');
+      if ($tabs.length) {
+        var notSmartPhone = window.matchMedia('(min-width: 300px)');
+        if (notSmartPhone.matches) {
+          $tabs.once('nav-tabs').each(init);
+        }
+      }
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/core/themes/seven/js/nav-tabs.js b/core/themes/seven/js/nav-tabs.js
index ba44ffb944c9..ec25c5201c17 100644
--- a/core/themes/seven/js/nav-tabs.js
+++ b/core/themes/seven/js/nav-tabs.js
@@ -1,10 +1,11 @@
 /**
- * @file
- * Responsive navigation tabs.
- *
- * This also supports collapsible navigable is the 'is-collapsible' class is
- * added to the main element, and a target element is included.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./themes/seven/js/nav-tabs.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
+
 (function ($, Drupal) {
 
   'use strict';
@@ -37,11 +38,8 @@
     $(window).on('resize.tabs', Drupal.debounce(handleResize, 150)).trigger('resize.tabs');
   }
 
-  /**
-   * Initialise the tabs JS.
-   */
   Drupal.behaviors.navTabs = {
-    attach: function (context, settings) {
+    attach: function attach(context, settings) {
       var $tabs = $(context).find('[data-drupal-nav-tabs]');
       if ($tabs.length) {
         var notSmartPhone = window.matchMedia('(min-width: 300px)');
@@ -51,5 +49,4 @@
       }
     }
   };
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/themes/seven/js/responsive-details.es6.js b/core/themes/seven/js/responsive-details.es6.js
new file mode 100644
index 000000000000..8fdb4530f3bd
--- /dev/null
+++ b/core/themes/seven/js/responsive-details.es6.js
@@ -0,0 +1,57 @@
+/**
+ * @file
+ * Provides responsive behaviors to HTML details elements.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * Initializes the responsive behaviors for details elements.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches the responsive behavior to status report specific details elements.
+   */
+  Drupal.behaviors.responsiveDetails = {
+    attach: function (context) {
+      var $details = $(context).find('details').once('responsive-details');
+
+      if (!$details.length) {
+        return;
+      }
+
+      function detailsToggle(matches) {
+        if (matches) {
+          $details.attr('open', true);
+          $summaries.attr('aria-expanded', true);
+          $summaries.on('click.details-open', false);
+        }
+        else {
+          // If user explicitly opened one, leave it alone.
+          var $notPressed = $details
+            .find('> summary[aria-pressed!=true]')
+            .attr('aria-expanded', false);
+          $notPressed
+            .parent('details')
+            .attr('open', false);
+          // After resize, allow user to close previously opened details.
+          $summaries.off('.details-open');
+        }
+      }
+
+      function handleDetailsMQ(event) {
+        detailsToggle(event.matches);
+      }
+
+      var $summaries = $details.find('> summary');
+      var mql = window.matchMedia('(min-width:48em)');
+      mql.addListener(handleDetailsMQ);
+      detailsToggle(mql.matches);
+    }
+  };
+
+
+})(jQuery, Drupal);
diff --git a/core/themes/seven/js/responsive-details.js b/core/themes/seven/js/responsive-details.js
index 8fdb4530f3bd..63d22b3a50ed 100644
--- a/core/themes/seven/js/responsive-details.js
+++ b/core/themes/seven/js/responsive-details.js
@@ -1,22 +1,17 @@
 /**
- * @file
- * Provides responsive behaviors to HTML details elements.
- */
+* DO NOT EDIT THIS FILE.
+* All changes should be applied to ./themes/seven/js/responsive-details.es6.js
+* See the following change record for more information,
+* https://www.drupal.org/node/2873849
+* @preserve
+**/
 
 (function ($, Drupal) {
 
   'use strict';
 
-  /**
-   * Initializes the responsive behaviors for details elements.
-   *
-   * @type {Drupal~behavior}
-   *
-   * @prop {Drupal~behaviorAttach} attach
-   *   Attaches the responsive behavior to status report specific details elements.
-   */
   Drupal.behaviors.responsiveDetails = {
-    attach: function (context) {
+    attach: function attach(context) {
       var $details = $(context).find('details').once('responsive-details');
 
       if (!$details.length) {
@@ -28,16 +23,10 @@
           $details.attr('open', true);
           $summaries.attr('aria-expanded', true);
           $summaries.on('click.details-open', false);
-        }
-        else {
-          // If user explicitly opened one, leave it alone.
-          var $notPressed = $details
-            .find('> summary[aria-pressed!=true]')
-            .attr('aria-expanded', false);
-          $notPressed
-            .parent('details')
-            .attr('open', false);
-          // After resize, allow user to close previously opened details.
+        } else {
+          var $notPressed = $details.find('> summary[aria-pressed!=true]').attr('aria-expanded', false);
+          $notPressed.parent('details').attr('open', false);
+
           $summaries.off('.details-open');
         }
       }
@@ -52,6 +41,4 @@
       detailsToggle(mql.matches);
     }
   };
-
-
-})(jQuery, Drupal);
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/yarn.lock b/core/yarn.lock
index 272049f8f845..d4bab4acd4ae 100644
--- a/core/yarn.lock
+++ b/core/yarn.lock
@@ -322,6 +322,10 @@ babel-messages@^6.23.0:
   dependencies:
     babel-runtime "^6.22.0"
 
+babel-plugin-add-header-comment@1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/babel-plugin-add-header-comment/-/babel-plugin-add-header-comment-1.0.3.tgz#511c4901062640d5a480b4ac3edd6944195850ec"
+
 babel-plugin-check-es2015-constants@^6.22.0:
   version "6.22.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a"
-- 
GitLab