Skip to content

Commit

Permalink
Merge pull request #32 from ckeditor/ck/improve-errors
Browse files Browse the repository at this point in the history
Feature: Improved error readability in `loadCKEditorCloud`. It should now detect existing editor instances and provide migration info from NPM to CDN.
  • Loading branch information
Mati365 authored Sep 24, 2024
2 parents be678f1 + c9f75bd commit a57de9c
Show file tree
Hide file tree
Showing 25 changed files with 498 additions and 55 deletions.
37 changes: 32 additions & 5 deletions src/cdn/ck/createCKCdnBaseBundlePack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@
* For licensing, see LICENSE.md.
*/

import type { CKCdnResourcesAdvancedPack } from '../utils/loadCKCdnResourcesPack';
import type { CKCdnResourcesAdvancedPack } from '@/cdn/utils/loadCKCdnResourcesPack';

import { waitForWindowEntry } from '@/utils/waitForWindowEntry';
import { injectScriptsInParallel } from '@/utils/injectScript';
import { without } from '@/utils/without';

import { getCKBaseBundleInstallationInfo } from '@/installation-info/getCKBaseBundleInstallationInfo';
import { createCKDocsUrl } from '@/docs/createCKDocsUrl';

import { createCKCdnUrl, type CKCdnVersion } from './createCKCdnUrl';
import { waitForWindowEntry } from '../../utils/waitForWindowEntry';
import { injectScriptsInParallel } from '../../utils/injectScript';
import { without } from '../../utils/without';

import './globals.d';

Expand Down Expand Up @@ -68,7 +72,30 @@ export function createCKCdnBaseBundlePack(

// Pick the exported global variables from the window object.
checkPluginLoaded: async () =>
waitForWindowEntry( [ 'CKEDITOR' ] )
waitForWindowEntry( [ 'CKEDITOR' ] ),

// Check if the CKEditor base bundle is already loaded and throw an error if it is.
beforeInject: () => {
const installationInfo = getCKBaseBundleInstallationInfo();

switch ( installationInfo?.source ) {
case 'npm':
throw new Error(
'CKEditor 5 is already loaded from npm. Check the migration guide for more details: ' +
createCKDocsUrl( 'updating/migration-to-cdn/vanilla-js.html' )
);

case 'cdn':
if ( installationInfo.version !== version ) {
throw new Error(
`CKEditor 5 is already loaded from CDN in version ${ installationInfo.version }. ` +
`Remove the old <script> and <link> tags loading CKEditor 5 to allow loading the ${ version } version.`
);
}

break;
}
}
};
}

Expand Down
9 changes: 5 additions & 4 deletions src/cdn/ck/createCKCdnPremiumBundlePack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
* For licensing, see LICENSE.md.
*/

import type { CKCdnResourcesAdvancedPack } from '../utils/loadCKCdnResourcesPack';
import type { CKCdnResourcesAdvancedPack } from '@/cdn/utils/loadCKCdnResourcesPack';
import type { CKCdnBaseBundlePackConfig } from './createCKCdnBaseBundlePack';

import { waitForWindowEntry } from '@/utils/waitForWindowEntry';
import { injectScriptsInParallel } from '@/utils/injectScript';
import { without } from '@/utils/without';

import { createCKCdnUrl } from './createCKCdnUrl';
import { waitForWindowEntry } from '../../utils/waitForWindowEntry';
import { injectScriptsInParallel } from '../../utils/injectScript';
import { without } from '../../utils/without';

import './globals.d';

Expand Down
4 changes: 3 additions & 1 deletion src/cdn/ck/createCKCdnUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
* For licensing, see LICENSE.md.
*/

import type { SemanticVersion } from '@/utils/isSemanticVersion';

/**
* The URL of the CKEditor CDN.
*/
Expand All @@ -11,7 +13,7 @@ export const CK_CDN_URL = 'https://cdn.ckeditor.com';
/**
* A version of a file on the CKEditor CDN.
*/
export type CKCdnVersion = `${ number }.${ number }.${ number }`;
export type CKCdnVersion = SemanticVersion;

/**
* Creates a URL to a file on the CKEditor CDN.
Expand Down
23 changes: 17 additions & 6 deletions src/cdn/ckbox/createCKBoxCdnBundlePack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
* For licensing, see LICENSE.md.
*/

import type { CKCdnResourcesAdvancedPack } from '../utils/loadCKCdnResourcesPack';
import { waitForWindowEntry } from '@/utils/waitForWindowEntry';
import { getCKBoxInstallationInfo } from '@/installation-info/getCKBoxInstallationInfo';

import { waitForWindowEntry } from '../../utils/waitForWindowEntry';
import type { CKCdnResourcesAdvancedPack } from '@/cdn/utils/loadCKCdnResourcesPack';
import { createCKBoxCdnUrl, type CKBoxCdnVersion } from './createCKBoxCdnUrl';

import './globals.d';
Expand Down Expand Up @@ -39,14 +40,24 @@ export function createCKBoxBundlePack(

// Load optional theme, if provided. It's not required but recommended because it improves the look and feel.
...theme && {
stylesheets: [
createCKBoxCdnUrl( 'ckbox', `styles/themes/${ theme }.css`, version )
]
stylesheets: [ createCKBoxCdnUrl( 'ckbox', `styles/themes/${ theme }.css`, version ) ]
},

// Pick the exported global variables from the window object.
checkPluginLoaded: async () =>
waitForWindowEntry( [ 'CKBox' ] )
waitForWindowEntry( [ 'CKBox' ] ),

// Check if the CKBox bundle is already loaded and throw an error if it is.
beforeInject: () => {
const installationInfo = getCKBoxInstallationInfo();

if ( installationInfo && installationInfo.version !== version ) {
throw new Error(
`CKBox is already loaded from CDN in version ${ installationInfo.version }. ` +
`Remove the old <script> and <link> tags loading CKBox to allow loading the ${ version } version.`
);
}
}
};
}

Expand Down
4 changes: 3 additions & 1 deletion src/cdn/ckbox/createCKBoxCdnUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
* For licensing, see LICENSE.md.
*/

import type { SemanticVersion } from '@/utils/isSemanticVersion';

/**
* The URL of the CKBox CDN.
*/
Expand All @@ -11,7 +13,7 @@ export const CKBOX_CDN_URL = 'https://cdn.ckbox.io';
/**
* A version of a file on the CKBox CDN.
*/
export type CKBoxCdnVersion = `${ number }.${ number }.${ number }`;
export type CKBoxCdnVersion = SemanticVersion;

/**
* Creates a URL to a file on the CKBox CDN.
Expand Down
4 changes: 2 additions & 2 deletions src/cdn/plugins/combineCdnPluginsPacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
* For licensing, see LICENSE.md.
*/

import { mapObjectValues } from '../../utils/mapObjectValues';
import { waitForWindowEntry } from '../../utils/waitForWindowEntry';
import { mapObjectValues } from '@/utils/mapObjectValues';
import { waitForWindowEntry } from '@/utils/waitForWindowEntry';

import {
normalizeCKCdnResourcesPack,
Expand Down
13 changes: 11 additions & 2 deletions src/cdn/utils/combineCKCdnBundlesPacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
* For licensing, see LICENSE.md.
*/

import { filterBlankObjectValues } from '../../utils/filterBlankObjectValues';
import { mapObjectValues } from '../../utils/mapObjectValues';
import { filterBlankObjectValues } from '@/utils/filterBlankObjectValues';
import { mapObjectValues } from '@/utils/mapObjectValues';

import {
normalizeCKCdnResourcesPack,
type CKCdnResourcesPack,
Expand Down Expand Up @@ -71,8 +72,16 @@ export function combineCKCdnBundlesPacks<P extends CKCdnBundlesPacks>( packs: P
return exportedGlobalVariables as any;
};

// Call beforeInject method of all packs.
const beforeInject = () => {
for ( const pack of Object.values( normalizedPacks ) ) {
pack.beforeInject?.();
}
};

return {
...mergedPacks,
beforeInject,
checkPluginLoaded
};
}
Expand Down
19 changes: 14 additions & 5 deletions src/cdn/utils/loadCKCdnResourcesPack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
* For licensing, see LICENSE.md.
*/

import type { Awaitable } from '../../types/Awaitable';
import type { Awaitable } from '@/types/Awaitable';

import { injectScript, type InjectScriptProps } from '../../utils/injectScript';
import { injectStylesheet } from '../../utils/injectStylesheet';
import { preloadResource } from '../../utils/preloadResource';
import { uniq } from '../../utils/uniq';
import { injectScript, type InjectScriptProps } from '@/utils/injectScript';
import { injectStylesheet } from '@/utils/injectStylesheet';
import { preloadResource } from '@/utils/preloadResource';
import { uniq } from '@/utils/uniq';

/**
* Loads pack of resources (scripts and stylesheets) and returns the exported global variables (if any).
Expand All @@ -32,9 +32,13 @@ export async function loadCKCdnResourcesPack<P extends CKCdnResourcesPack<any>>(
scripts = [],
stylesheets = [],
preload,
beforeInject,
checkPluginLoaded
} = normalizeCKCdnResourcesPack( pack );

// Execute the `beforeInject` callback if defined. It checks if the resources are already loaded.
beforeInject?.();

// If preload is not defined, use all stylesheets and scripts as preload resources.
if ( !preload ) {
preload = uniq( [
Expand Down Expand Up @@ -174,6 +178,11 @@ export type CKCdnResourcesAdvancedPack<R> = {
*/
htmlAttributes?: Record<string, any>;

/**
* Callback that is executed before injecting the resources. It can be used to verify if the resources are already loaded.
*/
beforeInject?: () => void;

/**
* Get JS object with global variables exported by scripts.
*/
Expand Down
15 changes: 15 additions & 0 deletions src/docs/createCKDocsUrl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md.
*/

import type { SemanticVersion } from '@/utils/isSemanticVersion';

export const CK_DOCS_URL = 'https://ckeditor.com/docs/ckeditor5';

/**
* Creates a URL to a file on the CKEditor documentation.
*/
export function createCKDocsUrl( path: string, version: SemanticVersion | 'latest' = 'latest' ): string {
return `${ CK_DOCS_URL }/${ version }/${ path }`;
}
25 changes: 25 additions & 0 deletions src/installation-info/getCKBaseBundleInstallationInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md.
*/

import type { BundleInstallationInfo } from './types';

import { isSemanticVersion } from '@/utils/isSemanticVersion';

/**
* Returns information about the base CKEditor bundle installation.
*/
export function getCKBaseBundleInstallationInfo(): BundleInstallationInfo | null {
const { CKEDITOR_VERSION, CKEDITOR } = window;

if ( !isSemanticVersion( CKEDITOR_VERSION ) ) {
return null;
}

// Global `CKEDITOR` is set only in CDN builds.
return {
source: CKEDITOR ? 'cdn' : 'npm',
version: CKEDITOR_VERSION
};
}
24 changes: 24 additions & 0 deletions src/installation-info/getCKBoxInstallationInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md.
*/

import type { BundleInstallationInfo } from './types';

import { isSemanticVersion } from '@/utils/isSemanticVersion';

/**
* Returns information about the CKBox installation.
*/
export function getCKBoxInstallationInfo(): BundleInstallationInfo | null {
const version = window.CKBox?.version;

if ( !isSemanticVersion( version ) ) {
return null;
}

return {
source: 'cdn',
version
};
}
27 changes: 27 additions & 0 deletions src/installation-info/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md.
*/

import type { SemanticVersion } from '@/utils/isSemanticVersion';

/**
* The source from which CKEditor was installed.
*/
type BundleInstallationSource = 'npm' | 'cdn';

/**
* Information about the currently installed CKEditor.
*/
export type BundleInstallationInfo = {

/**
* The source from which CKEditor was installed.
*/
source: BundleInstallationSource;

/**
* The version of CKEditor.
*/
version: SemanticVersion;
};
16 changes: 16 additions & 0 deletions src/utils/isSemanticVersion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md.
*/

export type SemanticVersion = `${ number }.${ number }.${ number }`;

/**
* Checks if the given string is a semantic version number.
*
* @param version - The string to check.
* @returns `true` if the string is a semantic version, `false` otherwise.
*/
export function isSemanticVersion( version: string | undefined | null ): version is SemanticVersion {
return !!version && /^\d+\.\d+\.\d+/.test( version );
}
Loading

0 comments on commit a57de9c

Please sign in to comment.