-
Notifications
You must be signed in to change notification settings - Fork 100
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Cloud integration #510
Cloud integration #510
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PoC is doing what should 👍
Few thoughts (applies to all frameworks):
We need to have on our mind that all 3rd party plugins need to be in UMD format - i mean, we know it, but we need to explicitly inform the community/integrators. If they use some plugins built for example by package-generator
but not migrated yet (built and published) - it will be a problem.
Another thought - what about with plugins that are not published on NPM? I know that it's some kind of rare edge case but we got one - ckeditor5-mermaid
, i know it's marked as experimental but do we want to only shrug to our potential clients?
In this particular case (ckeditor5-mermaid) we got dist
in repo with UMD build, so it can be downloaded and hosted by integrator.
const cloud = useCKEditorCloud({
version: '42.0.1',
plugins: {
YourPlugin: {
getExportedEntries: () => import( './your-plugin' ),
},
},
}); It can be done using async imports + proper |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tested it briefly, and it works fine. I also looked through the files and added some nitpicks.
Thx @gorzelinski! I applied fixes. |
ea8599e
to
cc086bc
Compare
Pull Request Test Coverage Report for Build 60d18044-4cac-4919-9da1-9826eb395ef7Details
💛 - Coveralls |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks Good. I left two small comments to consider.
ee86e30
to
4636d99
Compare
React CDN Integration
Related Vue integration: ckeditor/ckeditor5-vue#301
Related Angular integration: ckeditor/ckeditor5-angular#431
❓ Tl;dr
The
useCKEditorCloud
hook is designed for integratingCKEditor
from a CDN into React applications, aiming to simplify the process of addingCKEditor
by managing its asynchronous loading, handling errors, and providing access to the editor and its dependencies once loaded. This addresses the common challenge in React and other frameworks of integrating third-party JavaScript libraries, which involves managing asynchronous script loading and ensuring scripts and styles are loaded in the correct order without blocking the main thread.Additionally, the hook specifically tackles the challenges associated with loading
CKEditor
from its CDN. It optimizes the loading process by preventing redundant script injections and handling race conditions and caching effectively. This ensures that scripts are not injected multiple times if they are already present, which is crucial for applications that dynamically destroy and reinitialize components, includingCKEditor
, as users navigate. This approach enhances performance and user experience by ensuring efficient and error-free loading ofCKEditor's
assets from the CDN.Demos
Plain editor from CDN (with external plugins): http://localhost:5174/demos/cdn-react/index.html
Multiroot hook: http://localhost:5174/demos/cdn-multiroot-react/index.html
🔧 General format of the
useCKEditorCloud
hook callThe
useCKEditorCloud
hook is responsible for returning information that:CKEditor
is still being downloaded from the CDN withstatus = "loading"
.status = "error"
, then further information is returned in theerror
field.data
field and its dependencies whenstatus = "success"
.Simplest config
The simplest usage example that will load
CKEditor
fromCDN
and return its data incloud.data
:CKBox config
CKBox integration example:
Third party plugins config
A more advanced example that allows specify whether external stylesheets or scripts should be loaded:
Window
typings ofCKEditor 5
are not present. So we have to manually re-export them:which is problematic when we want use type with
prototype
(likeEventInfo
):So at this moment we have this:
and later:
🔧 Operation algorithm of
useCKEditorCloud
Dependency packages
Under the hood, the hook uses objects describing the dependencies of bundles served in the CDN. These objects define which styles and which scripts must be injected on the page to receive objects on the window returned by
getExportedEntries
. In other words, to download a bundle fromCKEditor5
, we pass the link to our CDN inscripts
, similarly instylesheets
, and ingetExportedEntries
we return thewindow.CKEDITOR5
object. This object will then be returned by the hook after loading is complete.Package format:
Currently, we have two pre-defined packages:
Defined by
createCKCdnBaseBundlePack
, the main editor package, which returns:Defined by
createCKCdnPremiumBundlePack
, the package with premium editor features, which returns:Depending on the provided configuration, the
useCKEditorCloud
hook merges these packages using thecombineCKCdnBundlesPacks
method and transforms them into this form:This form is extended by other UMD scripts (like
WIRIS
) and thanks to exports fromcreateCKCdnBaseBundlePack
, it allows specifying whether users want to download only styles, only JS, or both. By merging, we get the ability to load allcss
styles andjs
scripts before starting to load the main code.HTML Injection
Preload
At the moment of the first render of the
App
component, theuseCKEditorCloud
will inject tags responsible forrel=preload
into thehead
:This informs the browser that while downloading
.css
files, it can also start downloading.umd.js
files. This counteracts a dependency cascade where we would have to wait for the mainCKEditor
bundle to load, then theCKEditor Premium Features
bundle, and then some custom plugin code before starting the editor initialization.preload
loads them all in parallel. This is noticeable on slower connections, it is not required for the implementation itself, but significantly improves UX by about 500-600ms on load atFast 3G
speed.Target resources
When the hook has preloaded the resources, it proceeds to load the target scripts and creates a
Promise
from this. This is problematic because React does not support asynchronicity in components in thestable
version. In theunstable
version, it has a hookuse
, which currently cannot be used because we supportReact 16
, which does not support it.At this stage, it looks like this:
Dependency packages are merged. All CSS styles are loaded, then JS, and finally plugins.
Handling Asynchrony in React
Since the handling of
Promise
inReact
withoutsuspense
does not exist, it is managed through a customly added hookuseAsyncValue
. Its usage looks roughly like thisAfter finishing loading, it will return:
However, the hook also has
loading
,idle
, anderror
states. The hook, with each change ofserializedConfigKey
, which is the config's form serialized to a string, reloads the data from the CDN. This allows controlling the loading of premium plugins and recreating the editor.Handling race-conditions and cache
Each injecting function has a verification whether the script has already been embedded or not. Example for JS tags:
thanks to this, destroying the component and initializing it at the moment when it was in the process of injecting the script does not cause problems and works. Navigation between pages that also have the same dependencies has been accelerated. This gives us the possibility to have 2 editors on the site - one having premium features, the other not.
🔧 General format of the
withCKEditorCloud
HOCThe main reason for the creation of this
HOC
is thehook
useMyMultiRootEditor
. Thanks to it, we avoid a situation where we have to add conditional rendering in theuseMyMultiRootEditor
hook:In theory, modifying
useMultiRootEditor
is possible, but it would potentially cause problems with race-conditions and the stability of the integration itself at the time of quick loading flag switchescloud
. UsingHOC
, we avoid conditioning, and the embedding component already has thecloud
injected with initializedCKEditor
constructors: