Problem/Motivation
https://github.com/ckeditor/ckeditor5/releases/tag/v38.2.0-alpha.1
⏳ Waiting for https://github.com/ckeditor/ckeditor5/releases/tag/v38.2.0
https://github.com/ckeditor/ckeditor5/releases/tag/v39.0.0
This release adds support for empty inline elements, which is a data loss critical and one of the last few remaining top priorities in #3340578: [meta] [upstream] Prioritized CKEditor 5 upstream blockers. The lack of this support is a blocker for many downstream projects. See #18 + #19 + #21 for details.
⚠️ This is a major version release, but the only BC break is in CKBox, which no one in the Drupal world uses. So, it’s not a breaking change for the Drupal ecosystem.
(A separate 38.2.0 release for the CKEditor 5 users not using CKBox would add so much unnecessary workload to the team that they could not justify this, see #10 by @Reinmar from the CKEditor 5 team.)
Proposed resolution
- Update
core/package.json cd coreyarn installyarn buildyarn build:ckeditor5-types
Remaining tasks
None.
User interface changes
None.
API changes
None.
Data model changes
None.
Release notes snippet
CKEditor 5 has been updated to 39.0.0. This fixes a long-standing data loss critical bug: until now, CKEditor 5 stripped all inline empty elements, which are typically used for inline icons using markup such as <i class="icon icon-druplicon"></i> or <span class="icon icon-druplicon"></span>. Going forward, this is supported, but requires the Source Editing plugin to be configured to allow <i class> or <span class>, respectively. This must be configured manually, which is better than in CKEditor 4, where it required custom code.
Issue fork drupal-3377562
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
dk-massive commentedAs of this writing 38.2.0 is not at a stable release.
Comment #3
wim leersThere's no
38.2.0stable release yet, only an alpha: https://github.com/ckeditor/ckeditor5/releases/tag/v38.2.0-alpha.1 😅But +1 to everything you said 😊
Note that this will never be committed to
10.0.x, only to10.1.x.Comment #4
wim leersComment #5
wim leersAll changes must first land in
11.x.Comment #6
catchIf this really doesn't break any contrib integrations, I think we should backport it to 10.1.x, but let's get it into 11.x first.
Comment #8
wim leersComment #9
smustgrave commentedReran tests twice but they consistently fail.
Comment #10
Reinmar commentedA bit more context on the versioning from our side:
CKE5 ecosystem is around ~100 pkgs now. In the past, we versioned them independently, just like semver dictates. Some were in e.g. 5.0.0, other, that changed slower were 1.1.10 let's say. All good.
But, that created an awful DX – you could never know which packages are actually meant to work together. It'd be fine if you were using the very latest versions of all of them, but sometimes you can't. So, when trying to hardlock to some older CKE5 version, you'd need to somehow figure out versions of all CKE5 packages that were shipped at the same time.
Theoretically, you may ask, why is it necessary to get those shipped at the same time if you use semver and track all BCs? Couldn't I get some of the latest, but keep some of the older ones if... well, if what?
Let's welcome npm on stage. And transient dependencies.
Let's say you use two CKE5 features – FeatA and FeatB. cke5-featA@1.0.0 requires cke5-engine in version ^1.0.0, cke5-featB@1.0.1 relies on a newer version of the engine (^2.0.0) as it was released a year later. As a developer integrating CKE5 you'll only see the versions of featA and featB pkgs. So you think all's good.
What will npm do if you try to install this setup?
It will install the cke5-engine package twice to satisfy both feature packages needs. That makes some sense in Node.js, for which npm was built, although, causes issues there too from time to time. But it makes zero sense for the web, where you don't want and very often cannot include one library twice in different versions. Hence our "ckeditor-duplicated-modules" check.
This isn't a problem isolated to CKEditor 5 itself. We've seen people running into similar issues with other projects too. And we researched how did some of them resolved this madness.
The most sane resolution was to ditch semver. It kinda works (but again, not often) for libraries, but makes no sense for ecosystems of packages. I don't remember anymore which projects we followed (I think Babel and Angular) but we realized that by having all our pkgs in the same version we're resolving many integrators issues. (EDIT: To add to that – very recently we started hardlocking transient dependencies to specific versions instead of using the so-called caret-ranges to fix another npm's problem).
Those changes helped a lot and we've seen a decline in issue reports showing mismatched versions of CKE5.
But then, the side effect is that a braking change in one package needs to somehow be reflected in the version of all packages. Something that happened in this case. A very narrowly used CKBox package had to have a BC due to a new major version of CKBox itself that had to rebuild its configuration. This isn't a widely used package yet so investing our limited capacity into building compat layer and/or shipping this without a BC on CKE5 side could not be justified. It's literally cheaper to go on a call with everyone using this package and resolving this for them :D
So, my recommendation is to analyze the changelog of CKE5 itself where we're trying (not always successfully, but we're hopefully improving on this) to list all BCs (that we divide into two categories based on their impact) and figure out whether this is a major or minor release for your use case. The version number of CKEditor 5 is a secondary thing and I started thinking we should use only the X of X.Y.Z :D
Comment #11
spokjeLooking into test-failures.
Comment #12
spokjeLooks like CKEditor replaced the inner workings of
<figcaption>-element in 39.0.0.Where it was a
data-placeholder-attribute with the text in it's value, now the text lives inside the<figcaption>-element.So at the very least the now failing
\Drupal\Tests\ckeditor5\FunctionalJavascript\MediaTest::testEditableCaptionneeds refactoring to adapt to those changes.Unsure if this is an intentional CKEditor5 behaviour change or not, so let's await a verdict on that before actually changing it.
Comment #13
wim leersThe failure:
Debugged test with:
Unfortunately there is a legitimate regression here:
i.e. what I see is worse than what @Spokje describes: the placeholder text is just gone for me? 😅
Comment #14
wim leersLooks like this is likely a consequence of this minor breaking change in the changelog:
— https://github.com/ckeditor/ckeditor5/issues/9925 → https://github.com/ckeditor/ckeditor5/commit/a7e094703b17c4ca29beb4d85ec...
We do
in
core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/drupalmediacaption/drupalmediacaptionediting.js.Comment #15
wim leers👍 Just pushed a fix per #14.
👍 Fortunately, not a single Drupal contrib module has JS that calls
enablePlaceholder(), so it should still be safe to update to CKEditor 5 39 despite this BC break. The fact that it solves #3337298: [upstream] [GHS] CKEditor 5 removes empty inline elements alone has far more positive Drupal ecosystem impact than the potential negative.😰 But … in a basic manual test, I see that there's a hard crash when adding the
drupalMediabutton to the text editor, that causes a crash even before the editor boots (meaning: it never even shows up):Debugging that now.
Comment #16
spokjeThat's because @Spokje described it rather poorly, luckily you found it yourself. 😅
Comment #17
wim leersgetClosestSelectedDrupalMediaWidget()was last refined ~1.5 year ago in #3264775: [drupalMedia] Toolbar should be visible when element inside <drupalMedia> is focused (back when we were stabilizing CKEditor 5!) and looks like this:What's happening is that
selection.getFirstPosition()returnsnulland hence the.parentexpression causes a fatal error.What's missing is:
Curiously:
getFirstPosition()has been documented to returnnullif there's no range in the selection since 7 years ago.getFirstPosition()behaving differently in the CKE5 39 release notes, although it has many mentions ofSelection-related changes38.1.0,Selection.getFirstPosition()returns the first element in the document, even when there is NO SELECTION 😳 — and that is what39.0.0changes and arguably fixesFunctionalJavascripttest powered bychromedriver: this does NOT happen, only when using Chrome directly/in manual testing 😳, because inside achromedriver-booted browser, even manual debugging steps show the behavior described above for38.1.0!So we seem to have ourselves a https://en.wikipedia.org/wiki/Heisenbug, although it's one with a trivial and safe fix… 🙃
(All using Chrome 115.)
Discussing this with the CKEditor 5 team in a few hours 👍
Comment #18
wim leershtmlSupport.allowEmptyis a new configuration option that will allow us to solve the #3337298: [upstream] [GHS] CKEditor 5 removes empty inline elements data loss critical. In manual testing I can already confirm it works 👍What's missing: test coverage. Tackling that first over at #3337298-22: [upstream] [GHS] CKEditor 5 removes empty inline elements.
Comment #19
wim leersSince markup like
<i class="fab fa-drupal"></i>or<span class="icon my-icon"></span>can only be created through theSourceEditingCKEditor 5 plugin, I added the necessary test coverage there: #3337298-23: [upstream] [GHS] CKEditor 5 removes empty inline elements.Copying the test from that issue into this MR, because it's necessary to justify this update: this either fixes a critical data loss bug or it doesn't.
First, let's verify that the test fails even on version 39 of CKEditor 5, because the test alone of course does not specify the
htmlSupport.allowEmptyconfig.Comment #21
wim leersI discussed #19 in detail with @Reinmar and @witeksocha.
Conclusion:
So, adjusting the test coverage slightly for that: even for unrestricted text formats, we should expect
to be transformed to
Cherry-picked #3337298-25: [upstream] [GHS] CKEditor 5 removes empty inline elements here 👍
Comment #22
wim leersAnd now the actual fix for #3337298: [upstream] [GHS] CKEditor 5 removes empty inline elements, which is possible only here, thanks to CKEditor 5
39containing the upstream fix that we must correctly configure 🤓The fix is very simple: configure
htmlSupport.allowEmpty, with the list of HTML tags that is configured in the Source Editing view. That means this is must be manually configured. There is no automatic upgrade path. Again, #3337298-7: [upstream] [GHS] CKEditor 5 removes empty inline elements is crucial here: in CKEditor 4 it didn't require manual configuration, but custom code!Added release notes for that reason — for both the next minor as well as the next patch release.
Comment #23
wim leersFollowing up on the Heisenbug I discovered in #17:
ckeditor5-imagepackage, where thedrupalMediaplugin had copied this pattern from. See https://github.com/ckeditor/ckeditor5/issues/11972.isFocusedstate was waiting until a 50 ms timeout had passed or the selection was changed, that was changed in https://github.com/ckeditor/ckeditor5/pull/14648. Thanks again to Kuba for pinpointing this! 👏So there indeed was a slight behavior change, but even then, the bugfix here (https://git.drupalcode.org/project/drupal/-/merge_requests/4530/diffs?co...) is still necessary — we basically got lucky that we didn't trigger this bug until now 😅
Also: @Spokje said this in Slack:
Which again just confirms that it's very likely a browser execution timing thing which we should not be relying on 👍
Issue summary updated, ready for final review now!
Comment #24
spokjeYou might want to fix the remaining test failure first? 😈😇
Comment #25
wim leersI forgot to adjust the expectations for dynamic plugin configuration
Unittest coverage inSourceEditingPluginTest, fixed 👍The test that really mattered and was failing before,
\Drupal\Tests\ckeditor5\FunctionalJavascript\SourceEditingTest::testEmptyInlineElement()was in fact already passing! 😊 That's aFunctionalJavaScripttest, so much more important.EDIT: cross-posted with @Spokje in #24 😅
Comment #26
smustgrave commentedTest updates look good. I took a look at #3337298: [upstream] [GHS] CKEditor 5 removes empty inline elements but should that be postponed first until this lands.
Comment #27
wim leers#26: #3337298: [upstream] [GHS] CKEditor 5 removes empty inline elements is adding only test coverage — and I've included that test coverage in this MR. If this MR gets merged, then we can mark that other issue as too 😊
Comment #29
longwaveCommitted f5de3b7 and pushed to 11.x. Thanks!
Patch does not apply cleanly to 10.1.x, leaving open for backport as previously discussed.
Comment #30
longwaveComment #31
wim leersOn it.
Comment #33
wim leersComment #34
smustgrave commentedFailure seems legit to the issue. FYI marking https://www.drupal.org/project/drupal/issues/3337298 as fixed too since it got merged 11.x
Comment #35
wim leersOdd, I literally cherry-picked the fix for that from the MR that was merged into11.x. I thought I had maybe built it wrong … but no,rm -rf'ing all built files and running theyarn install && yarn build && yarn build:ckeditor5-typescommands again results in an empty diff!I failed to cherry-pick it correctly 🙈
Passes tests now 👍
Comment #36
smustgrave commentedAll green!
Comment #37
catchI think this could use a change record, we could probably move the details about the empty inline elements to the CR then too.
IMO ckeditor is applying semver overly strictly and should give themselves a bit more leeway with @internal to reduce their major release frequency. Given that and that the only change required here fixed a hidden testing bug, I think we should get this into the next patch release and unblock ckeditor4 migrations a bit more.
Comment #38
wim leersDone: https://www.drupal.org/node/3379650.
RE "more @internal" — yes, I've had that exact conversation with @Reinmar 1–2 years ago. The struggle they have is the same Drupal had ~5 years ago and to some extent still has today: not explicitly marking what the actual public API is means that everything is an API 🙈 Their move to TypeScript recently should help with that, but in vanilla JavaScript there is no such thing as private/protected which makes this even harder for them!
Comment #39
wim leershttps://github.com/ckeditor/ckeditor5/releases/tag/v39.0.1 has been released, with the sole BC break that affected Drupal fixed: the
enablePlaceholder()thing mentioned in #14 and #15.I propose to first merge this MR as-is, and to open a new issue for
39.0.1? That will reduce risk further for the Drupal ecosystem.However, if core committers prefer that, I can instead update this
10.1.xMR to update to 39.0.1 immediately, but then I'll need a new MR for this issue against11.x, since the already merged one only updated us to 39.0.0.Comment #41
catchGiven it's fixing a bc break we already changed code for, and we're not going to change the code back, let's do a new issue. We've still got 2-3 weeks before the next 10.1 patch release so time to monitor a bit. CR looks good.
Committed 3cc755c and pushed to 10.1.x. Thanks!
Comment #42
wim leersDone: #3380637: Update CKEditor 5 to 39.0.1.
CR published (checked with @catch first).