Skip to content
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

🦸 Improving CKEditor 5 installation methods #15502

Closed
Witoso opened this issue Dec 11, 2023 · 45 comments
Closed

🦸 Improving CKEditor 5 installation methods #15502

Witoso opened this issue Dec 11, 2023 · 45 comments
Labels
support:2 An issue reported by a commercially licensed client. type:improvement This issue reports a possible enhancement of an existing feature.

Comments

@Witoso
Copy link
Member

Witoso commented Dec 11, 2023

Imagine you are setting up this cool editor for your app. You copied snippets from the docs, installed the packages, and quickly embedded the editor in your app. Everything works, you can type, and play with it. But now you start thinking about your specific use case. There’s just this one plugin that you want to add. You do it… and all hell breaks loose 🔥🔥🔥 Wall of errors, misleading docs. “I copied it straight from the docs! What’s wrong?! 😖 ” bangs in your head.

This is just one scenario of misleading onboarding to CKEditor 5. Which, you ask? The one where you extend a predefined build with additional plugins. 😮‍💨

Over the years, we learned a lot about the issues that you are facing. We resolved some and worked around some others, but the core issues remained there. We feel that it's now the right time to get to the root of it. 💪

For the past few months, we have researched the topic even further, focusing on these key issues:

  • We have several methods of installation. Each has its specific quirks that make using the documentation difficult.
  • The source packages that we release to npm need a specific setup, like webpack scripts, or a Vite plugin.
  • Certain popular environments require various workarounds to set up CKEditor 5 (e.g. server-side rendered (SSR) applications like Next.js, Nuxt.js, or SvelteKit).

The ultimate goal is to make setting up of CKEditor a pleasure, not a nightmare. It should feel like adding another library to your application — nothing more, nothing less. And we came up with a plan for how to do that.

Now, we need your help!

Share your experiences, ask us questions, correct our assumptions, and show your unique use cases. Finally, let us know if you like the proposed direction. 🙏

The RFC with the details is in the first comment below. Join us on this exciting ride!🎢

@Witoso Witoso added the type:improvement This issue reports a possible enhancement of an existing feature. label Dec 11, 2023
@filipsobol
Copy link
Member

filipsobol commented Dec 11, 2023

As we explained in the first comment, our current setup methods are not optimal. In this RFC, we will dive deeper and expand on our thought process and proposal.

Problems 😓

WYSIWYG editors are one of the most complex frontend projects and this affects current CKEditor 5 installation methods. Moreover, we have added some complexity due to the lack of standardization in the JavaScript ecosystem when we created this project, browser limitations, and most importantly, technical debt.

We feel it is time to remove some of that complexity.

The build step

Currently, the editor code requires a special build step to resolve non-standard CSS and SVG imports and to automatically add required theme styles and translations. Also, the editor itself does not allow dynamic plugin registration.

The consequence of these two technical limitations is that we ended up with multiple distribution options, each serving a different need:

  1. Distribution of pre-built editors with a fixed set of plugins, but without the ability for you to add more of them.
  2. Distribution of complete webpack and Vite setups with CKEditor-specific plugins needed to build the editor that allow you to select the plugins you need and remove everything else from the bundle.
  3. Distribution of DLLs that are pre-built and give you full control over what is included, but depend on the webpack-specific plugin and logic.

In other words, you either have control over the editor bundle or the developer tools you use. This is certainly not typical for a modern library, as you should have control over both.

Deviation from standards and best practices

There are a few areas where we deviate from standards and best practices:

  • Although we use the ESM system, our packages are not shipped as valid ESM packages. This causes problems in Vite.
  • Our code contains side effects and calls browser-specific globals even before the .create() method is called. This causes issues in server-side rendered applications.
  • The JavaScript bundles include inlined CSS. You get everything in one import, but:
    • They cause errors in server-side rendered applications because there is no global document object needed to inject the CSS into the DOM.
    • They make it difficult to style the editor, which is limited to using CSS variables.
    • They impact performance by forcing browsers to do extra work compared to using plain CSS.
    • They prevent parallelization of downloads.
  • Icons can only be changed using the webpack plugin.
  • Translations use the global object. You do not have to register them in the editor configuration, but:
    • They cause errors in server-side rendered applications because there is no access to the global window object.
    • They cause side effects.
    • They pollute the global scope.

Note

We do not plan to support server-side rendering of CKEditor 5 itself. We only want to fix errors thrown by the CKEditor code when other applications render on the server side.

Minor annoyances

There are other smaller issues, like:

  • The ckeditor-duplicated-modules error caused by mistakenly installing plugins in different versions.
  • Difficulty creating universal copy-and-paste code snippets in the documentation that would cover all possible setups.

Goals 🎯

Once we identified the problems, we started looking at how the latest standards and best practices could help us solve them. This also included researching how other popular and modern JavaScript libraries are built and shipped, and what are some of the pain points for their authors and users. This helped us define our goals.

Improve the developer experience

Developer experience is difficult to measure, but you know when a library is doing it right. We currently lack good developer experience when it comes to the editor setup, but we feel like we know what the right direction is to significantly improve it:

  • Be more explicit and avoid magic and side effects. Nothing should happen without the developer's knowledge because things that happen magically are confusing.
  • Adhere to standards because they are a safer bet than custom solutions. They can help avoid unexpected problems with new tools introduced in the future. Standards are also well known to most developers.
  • Distribute ready-to-use code that does not require custom processing, runs directly in the browser, and works the same regardless of the setup (npm or CDN).

Stick with the standards

CKEditor should stand out for its great and unique features, not for its funky installation methods. With this in mind:

  • The distributed JavaScript should:
    • Be distributed as a valid ESM.
    • Be standard JavaScript that does not require CKEditor-specific plugins, so it runs in any project and works with any bundler.
    • Not contain SVG imports, but have them converted to strings.
    • Have no side effects, nor access any browser-specific globals (like document or window) before the .create() method is called to avoid problems when using the editor in server-side rendered applications.
  • The distributed CSS should be:
    • Standard CSS that does not require processing with PostCSS.
    • Distributed separately from JavaScript to allow parallel downloading and easy overriding.
  • The SVG icons should be easy to change using the editor configuration.
  • The translations should not create a global object and pollute the scope. They should be passed to the editor configuration.
  • The ckeditor5 package should act as a central point for developers and export everything from all open-source packages to:
    • Reduce the number of dependencies that need to be synchronized to avoid the ckeditor-duplicated-modules error.
    • Eliminate the need to install new packages when extending the editor with new plugins.
    • Hide implementation details and a huge package ecosystem.
    • Aggregate CSS and translations to avoid having to install them separately for each plugin.
    • Improve the CDN loading performance, as one large download is faster than many smaller ones, even when using HTTP/2.

Future 🚀

The goals described above will allow us to reduce the number of installation methods to just two: npm and CDN. This is what the CKEditor setup will likely look like in both methods after the changes.

npm setup

Below is the minimal setup you will need to create the editor in a regular JavaScript or TypeScript project. There are a few things to note about this example:

  • All plugins are imported from the ckeditor5 package.
  • Translations are explicitly added to the editor configuration object.
  • Styles are separated from the JavaScript bundle and explicitly imported.
  • No special setup is required. You can run this code in any frontend metaframework or build it with any bundler without any CKEditor-specific plugins.
import { ClassicEditor, Essentials, Paragraph } from 'ckeditor5';
import translations from 'ckeditor5/translations/pl.js';

import 'ckeditor5/styles.css';

await ClassicEditor.create( document.querySelector( '#editor' ), {
  plugins: [
    Essentials,
    Paragraph,
  ],
  toolbar: {
    items: [ 'undo', 'redo' ]
  },
  translations
} );

CDN setup

Below is the same setup using the CDN solution. Same as above, in this setup:

  • All plugins are imported from a single URL.
  • Translations are explicitly added to the editor configuration object.
  • Styles are separated from the JavaScript bundle and added explicitly.

This setup uses importmaps. This allows you to use the same import paths inside the <script> tag as in the npm setup.

<link rel="stylesheet" href="<CDN_LINK>/ckeditor5/dist/styles.css">

<script type="importmap">
{
  "imports": {
    "ckeditor5": "<CDN_LINK>/ckeditor5/dist/index.min.js",
    "ckeditor5/": "<CDN_LINK>/ckeditor5/dist/"
  }
}
</script>
<script type="module">
import { ClassicEditor, Essentials, Paragraph } from 'ckeditor5';
import translations from 'ckeditor5/translations/pl.js';

await ClassicEditor.create( document.querySelector( '#editor' ), {
  plugins: [
    Essentials,
    Paragraph,
  ],
  toolbar: {
    items: [ 'undo', 'redo' ]
  },
  translations
} );
</script>

Easier icons overriding

To simplify overriding the editor icons, we will introduce a new icons configuration option. It takes an object where the key is the icon identifier and the value is the stringified SVG.

await ClassicEditor.create( document.querySelector( '#editor' ), {
  // Other options
+  icons: {
+    bold: '<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">...</svg>'
+  }
} );

No more CKEditor-specific setups

Once the code we distribute no longer requires custom processors, you can remove CKEditor-specific plugins from your webpack or Vite setup. If you already have a JavaScript or TypeScript project, you can remove the dedicated CKEditor setup and import the editor directly inside your project.

Vite

-import { createRequire } from 'node:module';
-const require = createRequire( import.meta.url );

import { defineConfig } from 'vite';
-import ckeditor5 from '@ckeditor/vite-plugin-ckeditor5';

export default defineConfig( {
    plugins: [
-        ckeditor5( { theme: require.resolve( '@ckeditor/ckeditor5-theme-lark' ) } )
    ]
} );

webpack

- const { bundler, styles } = require( '@ckeditor/ckeditor5-dev-utils' );
- const { CKEditorTranslationsPlugin } = require( '@ckeditor/ckeditor5-dev-translations' );

module.exports = {
  // Some configuration options are skipped for readability.

  plugins: [
-    new CKEditorTranslationsPlugin( {
-      language: 'en',
-      additionalLanguages: 'all'
-    } ),
-    new webpack.BannerPlugin( {
-      banner: bundler.getLicenseBanner(),
-      raw: true
-    } )
  ],

  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
-          {
-            loader: 'postcss-loader',
-            options: {
-              postcssOptions: styles.getPostCssConfig( {
-                themeImporter: {
-                  themePath: require.resolve( '@ckeditor/ckeditor5-theme-lark' )
-                },
-                minify: true
-              } )
-            }
-          }
        ]
      }
    ]
  }
};

Fewer dependencies

Because the ckeditor5 package contains all open-source packages, you can remove all other packages from your dependencies. We will also distribute a similar package for our commercial plugins.

"dependencies": {
-  "@ckeditor/ckeditor5-editor-classic": "42.0.0",
-  "@ckeditor/ckeditor5-essentials": "42.0.0",
-  "@ckeditor/ckeditor5-highlight": "42.0.0",
-  "@ckeditor/ckeditor5-list": "42.0.0",
-  "@ckeditor/ckeditor5-paragraph": "42.0.0",
-  "@ckeditor/ckeditor5-table": "42.0.0",
-  "@ckeditor/ckeditor5-theme-lark": "42.0.0",
   "ckeditor5": "42.0.0"
}

DLLs and predefined builds

Until now, predefined builds and DLLs were a good choice for those who wanted to avoid the build step. The main difference between them is that predefined builds do not give you any control over the included plugins, while DLLs do.

Since the new setup methods no longer require a build step while still giving you full control over the plugins used, there will be no need to use predefined builds and DLLs. We plan to slowly deprecate them.

You can find more details in the RFC dedicated to DLLs.

Initial delivery plan

At the time of writing this RFC, we have already passed the proof-of-concept stage with promising results. We have also begun the process of rewriting and restructuring our documentation.

Unfortunately, we have found that it is not possible to introduce new setup methods without introducing some breaking changes. We have tried to minimize the impact on existing projects by phasing them in so that the effort required does not block or discourage you from upgrading.

This project is expected to be delivered in the first and second quarters of 2024.

Phase 1. Foundation

In the first phase, we will introduce breaking changes necessary to ship the new installation methods. These changes will allow us to continue supporting projects created using the old methods. In it, we will:

There may be other breaking changes in this release unrelated to this RFC.

Phase 2. Release

In second phase we will introduce the new setup methods. They will be opt-in and the old ones will continue to work, but we are still discussing for how long. However, from then on, we will start promoting the new methods as defaults. We will also work with package authors to help them migrate their plugins.

Breaking changes in this release only apply to projects that upgrade to the new setup methods.

In this release, we will:

  • Export the editor core and all open-source packages from the ckeditor5 package.
  • Extract styles from JavaScript bundles into separate CSS files.
  • Change the way translations are generated. They will need to be passed to the editor configuration instead of relying on the global object.
  • Allow overriding icons using the editor configuration.

Phase 3. Elevation

In the last phase, we will continue to improve the developer experience by addressing issues with using the editor in server-side rendered applications, improving error handling, and potentially upgrading released code to ES2022 to improve performance and readability.

"In the meantime"

We are also very excited about a companion project that will simplify the installation and setup process even more. It will remain a secret for now 🤫.

Probable migration paths 🛣️

Your migration path will depend on your current setup and whether you use the CDN or npm as the new method. In the migration paths described below, we have made assumptions about which version you are more likely to use.

The migration paths may change slightly before the final release.

CDN

Currently, our CDN only distributes predefined builds with a known list of plugins. After the changes, we will distribute the ckeditor5 package containing all the open-source plugins, so you have full control over which ones you use, as well as loaded styles, and translations.

Predefined builds have a known set of plugins, so with the release, we will provide code you can copy and paste in place of the old setup. There will thus be no extra work on your end.

Some of you may wonder how bundling all the packages together will affect the size of the distributed file. The good news is that it will not change much. The predefined builds already include the editor's core and the heaviest plugins. We estimate that the new approach will increase the download size by about 20%, although the exact number is not yet known. However, we are considering a way to generate a personalized CDN URL with only the code you need and everything else removed.

Predefined builds

As with the CDN, the predefined builds have a known list of plugins, so with the release, we will provide code you can copy and paste in place of the old setup. There will thus be no extra work on your end.

However, if you are using a frontend metaframework (like Next.js, Nuxt, SvelteKit) or bundler (like webpack, Rollup, Vite), then unlike the CDN, it should remove all unused code, likely resulting in a smaller bundle compared to the current predefined builds.

Customized builds / integrating from source / online builder

Custom builds no longer require a separate webpack or Vite setup. If you want to continue using them, you can. The CKEditor-specific plugins will no longer be needed.

However, if you would like to remove this setup, and you are already using a frontend metaframework (like Next.js, Nuxt, SvelteKit) or bundler (like webpack, Rollup, Vite), you can import the CKEditor code directly into your project.

When it comes to the editor code, you will need to:

  • Remove all CKEditor dependencies except for the ckeditor5 package.
  • Import all plugins from ckeditor5 instead of individual packages.
  • Add the CSS import.

We will provide code examples to help you migrate, but the exact configuration will depend on the plugins you use.

DLLs

The most notable difference between the new installation methods and DLLs is that the new approach will not expose the CKEditor5 global object.

Other than that, as shown in the "Future" section, you will also need to:

  • Import plugin and editor classes from the ckeditor5 package instead of relying on the CKEditor5 global object.
  • Add the CSS import.

We will provide code examples to help you migrate, but the exact configuration will depend on the plugins you use.

You can find more details in the RFC dedicated to DLLs.

Plugin authors

For those of you using the package generator to create custom plugins, we will introduce a new command that will build the package for the new format. The command will output a dist folder containing a JavaScript bundle, and optionally a separate CSS and global-free translation files.

You will need to decide if you want to support both the old and new setup methods or just the new one. Based on this, the old setup may still work, but the setup method for the new build will be similar to what we showed in the "Future" section, and will require you to:

  • Register a plugin itself (no change here).
  • Import CSS styles if your plugin has custom styles.
  • Add translations to the editor configuration if your plugin includes them.
+ import { MyPlugin } from '<path_to_plugin>';
+ import MyTranslations from '<path_to_plugin>/translations/<lang_code>.js';

+import '<path_to_plugin>/styles.css';

await ClassicEditor.create( document.querySelector( '#editor' ), {
  plugins: [
    MyPlugin
  ],
  translations: [
+    MyTranslations,
    // Other translations.
  ]
} );
</script>

Feedback 💬

We've done our best to thoroughly examine the existing problems, consider dozens of integration scenarios that we know of, research how other libraries and components have approached the problems, and finally how we can apply the modern standards to our specific needs. However, we may have missed something, and that is where we need your feedback.

Let us know if you like the proposed changes, if you have questions or doubts, or just react with 👍 or 👎 below the RFC so we know if we are on the right track.

We'll adjust the RFC in response to your comments or as the project evolves. To keep everyone up to date, we will add a new comment below whenever we make changes.

@filipsobol
Copy link
Member

We understand that theory can make some concepts difficult to understand, and some of you may want to try out the new installation methods right away. That's why we've prepared a small demo that shows how the new installation method works with Vite and Webpack, but you can also run it in your setup. For more information, check out this repository, but please note that this demo is limited and by no means production-ready.

@Witoso Witoso pinned this issue Dec 11, 2023
@Reinmar Reinmar changed the title 🦸 Improving CKEditor 5 installation methods 🦸 [RFC] Improving CKEditor 5 installation methods Dec 11, 2023
@Witoso
Copy link
Member Author

Witoso commented Dec 12, 2023

Happy to tag, and invite users that over past years contributed to the development through issues, comments, and feedback: @huzedong2015 @mmichaelis @dtdesign @slotterbackW @lauriii @wimleers @star-szr @Inviz @Kocal @urbanspr1nter

@Kocal
Copy link
Contributor

Kocal commented Dec 12, 2023

Hi 👋🏻

I skimmed through all of this and saw some interesting things, I'll read better later after work :)

@dtdesign
Copy link

I really appreciate the vast amount of thoughts you put into this to address some existing pain points. 👍

The icons are a real pain point because it is common for sites to use an existing icon set and although I do like most of the editor icons, sometimes it makes more sense to swap them out for a unified appearance. Replacing icons with a HTML string is a great idea because it is dead simple and flexible at the same time, for example we use a custom web component <fa-icon> to embed icons.

I’m also glad to see that you are taking steps to extract the CSS from the bundle. During a public beta test I did notice that the style tags caused a significant slow-down because it would trigger style calculations plus having to start the browser’s CSS parser a bazillion times isn’t cheap. However, that is already possible today with the MiniCssExtractPlugin that exports the CSS into a single file: https://github.com/WoltLab/editor/blob/4890e96907757b66c2ea29dfdb4f92fc6692d79d/webpack.config.js (LGPL v2.1, feel free to steal).

While you are at it, I also added the postcss-hover-media-feature to the built step to put all :hover selectors behind @media (hover: hover). It is very well supported in both recent and somewhat dated browsers and it prevents all those hover effects from being a sticky pain on touch devices. It’s not exactly an issue with the installation method, but it sort of overlaps with the limited CSS customization options.

Lastly, the plugins are a real issue for us. Our software is designed to run on plain web hosting accounts without access to a shell or similar. That means that the customer on average has no means to compile the bundle on their own, so whatever plugins we ship are set in stone. We solved this issue by exposing the bundled classes through a custom export so that plugins for our software can dynamically define plugins for the editor by relying on all the exported classes. It’s a bit ugly but it works, however, I don’t know if this can be solved in a better way with webpack.

@Inviz
Copy link
Contributor

Inviz commented Dec 12, 2023

Looking forward for this to evolve into headless builds at some point. Ability to build without any ui plugins would be greatly appreciated.

@quicksketch
Copy link

This proposal gives me a lot of concern. The transition from CKE4 to 5 has already been extraordinarily painful. Now that CKE4 is EOL, most projects depending on CKE have made the jump. What I'm reading here is that now after finally figuring out CKE5, pretty much every plugin that extends CKE5 is going to need a substantial effort to keep working (probably forking for pre and post this change). It will depend on the migration tools/methods available, but at first blush this proposal sounds like it puts too much emphasis on a shiny, better future without enough regard to the existing projects that will be substantially disrupted.

From what I've seen in the CMS-world, the DLL builds are the only realistic compilation option currently, since any CMS with their own module/plugin system need to be able to enable CKEditor plugins without recompiling the main bundle. I'm not sure if it's implied in the RFC, but an additional goal should be:

  • Plugins can be distributed and loaded separately from the main compiled bundle, across directories and domains.

Plain vanilla JS plugins should be possible to be loaded without any compilation at all. If the CKEditor5 global is eliminated I'm concerned that won't be possible.

@urbanspr1nter
Copy link
Contributor

Happy to tag, and invite users that over past years contributed to the development through issues, comments, and feedback: @huzedong2015 @mmichaelis @dtdesign @slotterbackW @lauriii @wimleers @star-szr @Inviz @Kocal @urbanspr1nter

Thanks @Witoso - Will take a look at the end of week/weekend.

@Kocal
Copy link
Contributor

Kocal commented Dec 13, 2023

Hi again :)

I'm really happy for the CKEditor-specific setups removal, I know you did what you could, but building CKEditor 5 from source is a really big mess, specially if you want an optimized CSS file, and it's slow, surely due to the use of webpack?

Anyway, all this good news will allow me to lighten my configuration file like this 😄 :

const path = require('path');
const webpack = require('webpack');
-const { CKEditorTranslationsPlugin } = require('@ckeditor/ckeditor5-dev-translations');
-const { styles } = require('@ckeditor/ckeditor5-dev-utils');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
-const postCssContentStylesPlugin = require('./build/postcss/list-content-styles-plugin');
-const { CKEditorContentStylesPlugin } = require('./build/webpack/CKEditorContentStylesPlugin');
-const { CKEditorMinifyStylesPlugin } = require('./build/webpack/CKEditorMinifyStylesPlugin');

-/**
- * Custom Webpack configuration for CKEditor 5, as we want to keep it isolated from the rest of the application/toolchain.
- * It also extracts the content styles from the built editor CSS files, to a "content styles" CSS file.
- *
- * @see https://ckeditor.com/docs/ckeditor5/latest/installation/advanced/alternative-setups/integrating-from-source-webpack.html
- * @see https://github.com/ckeditor/ckeditor5/blob/master/scripts/docs/build-content-styles.js
- */

-const contentRules = {
-    selector: [],
-    variables: [],
-    atRules: {},
-};

-const postCssConfig = styles.getPostCssConfig({
-    themeImporter: {
-        themePath: require.resolve('@ckeditor/ckeditor5-theme-lark'),
-    },
-    minify: false,
-});
-postCssConfig.plugins.push(postCssContentStylesPlugin(contentRules));

module.exports = {
    entry: {
        'editors/default': './src/editors/default.ts',
        'editors/admin': './src/editors/admin.ts',
        'editors/front_forum': './src/editors/front_forum.ts',
        'editors/front_forum_admin': './src/editors/front_forum_admin.ts',
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js',
        library: {
            name: '[name]',
            type: 'umd',
        },
        clean: true,
    },
    devtool: false,
    resolve: {
        extensions: ['.tsx', '.ts', '.js'],
    },
    module: {
        rules: [
            {
                test: /.([cm]?ts|tsx)$/,
                loader: 'ts-loader',
            },
            {
                test: /ckeditor5-[^/\\]+[/\\]theme[/\\]icons[/\\][^/\\]+\.svg$/,
                type: 'asset/source',
            },
            {
                test: /ckeditor5-[^/\\]+[/\\]theme[/\\].+\.css$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader',
                    {
                        loader: 'postcss-loader',
                        options: {
                            postcssOptions: {
-                                ...postCssConfig,
                                config: false,
                            },
                        },
                    },
                ],
            },
        ],
    },
    plugins: [
-        new CKEditorTranslationsPlugin({
-            // See https://ckeditor.com/docs/ckeditor5/latest/features/ui-language.html
-            language: 'fr',
-            additionalLanguages: ['en', 'de', 'it', 'es', 'pl', 'nl'],
-            buildAllTranslationsToSeparateFiles: true,
-            packageNamesPattern: /([/\\]ckeditor5-[^/\\]+[/\\])|(src\/plugins\/[^/\\]+)/,
-            sourceFilesPattern: /([/\\]ckeditor5-[^/\\]+[/\\]src)|(src\/plugins\/[^/\\]+)[/\\].+\.[jt]s$/,
-        }),
        new MiniCssExtractPlugin({
            filename: '[name].css',
        }),
        // https://github.com/TypeStrong/ts-loader#usage-with-webpack-watch
        new webpack.WatchIgnorePlugin({
            paths: [/\.js$/, /\.d\.[cm]ts$/, /\.txt$/],
        }),
-        new CKEditorContentStylesPlugin({
-            contentRules,
-        }),
-        new CKEditorMinifyStylesPlugin(),
    ],
};

However, I have a have one concern about how translations will be loaded.

In our CMS app, we have many languages and therefore as many CKEditor translations .js files. We don't load every translations at once, but only the needed one, through a dynamic import:

async function loadEditor({ editor, language }) {
    import(`../build/ckeditor/dist/translations/${language}.js`);

    // editor loading ...
}

Do you think this would be possible with the proposed solutions?
Could translations accepts a Promise? Or should I wait my translation to be loaded before creating the Editor like this? :

async function loadEditor({ editor, language }) {
    const translations = await Promise.all([
        // `build/ckeditor` is a directory dedicated to build CKEditor
        import(`../build/ckeditor/dist/translations/${language}.js`),
        
        // and what about plugins's translations?
   ]);

    return await ClassicEditor.create('...', {
        translations,   
    });
}

Thanks! :)

@Witoso
Copy link
Member Author

Witoso commented Dec 13, 2023

Thanks for the comments so far 🙏, we will craft a lengthier response a bit later, and probably add details to the RFC body.

@quicksketch, thanks for the remarks! We know that DLLs are used in Drupal ecosystems, and other CMS platforms, and our clients use them as well. We haven't decided on the moment of the DLLs deprecation, but we are preparing for a long time of supporting them.

Plain vanilla JS plugins should be possible to be loaded without any compilation at all. If the CKEditor5 global is eliminated I'm concerned that won't be possible.

This is our goal as well, the new method will not be a blocker here, details will follow :)

@quicksketch
Copy link

Thanks @Witoso. That's a big relief. I'm not a JS expert, but this statement is what had me worried:

Import plugin and editor classes from the ckeditor5 package instead of relying on the CKEditor5 global object.

AFAIK, import statements require a path structure. The DLL approach has allowed plugins to extend CKEditor without knowing anything about where other libraries are located. I'll hold my concerns until there is further clarification.

@urbanspr1nter
Copy link
Contributor

urbanspr1nter commented Dec 14, 2023

@Witoso @filipsobol

Really good stuff! I think most of these are good proposals, and coincidentally, addresses a lot of the pain points I've had in the past! 😁

Overall summary of what I would appreciate seeing:

  1. Option to not include UI components when importing packages. Right now whenever we consume a plugin, we not only get the Editing but also the UI classes within the plugin. Is there some way to avoid this? For instance, hard to take advantage of a potential smaller bundle size as we cannot shake this off on import.
  2. Embedding SVG icons and CSS into the build is unnecessary - I really appreciate seeing the recognition in this concern. Again, another win on loading perf for integrators who need to have a small bundle size.
  3. Most importantly, I would like to see documentation which directly reflects how we should really build from source. I've been successful at this in the past, but there are some gotchas that I keep forgetting to help with updating the documentation with.

As you can probably notice, my main concern has always been the bundling of some of the UI elements as opposed to the process in creating the build, but off the top of my head:

I would like to have some clear way to avoid the dreaded duplicated-modules error. For us, we have a strange way of building the source, but it makes it hard to pull down other handy classes and utils and utilize (impossible really)

@filipsobol
Copy link
Member

@dtdesign

While you are at it, I also added the postcss-hover-media-feature to the built step to put all :hover selectors behind @media (hover: hover). It is very well supported
in both recent and somewhat dated browsers and it prevents all those
hover effects from being a sticky pain on touch devices.

Thank you, we will investigate how @media (hover: hover) can improve the mobile experience.

Lastly, the plugins are a real issue for us. Our software is designed to
run on plain web hosting accounts without access to a shell or similar.
That means that the customer on average has no means to compile the
bundle on their own, so whatever plugins we ship are set in stone. We
solved this issue by exposing the bundled classes through a custom export
so that plugins for our software can dynamically define plugins for the
editor by relying on all the exported classes. It’s a bit ugly but it
works, however, I don’t know if this can be solved in a better way with
webpack.

Although we will be encouraging the use of the ckeditor5 package, you can still import from individual plugins. When you're ready to migrate to the new installation method, you'll need to append all imports with /dist/index.js (e.g. @ckeditor/ckeditor5-alignment/dist/index.js) and import CSS styles and translations as shown in the RFC. This is a good way if you want to migrate, but also want to minimize changes in your project.

However, our new CDN build may be ideal for your use case because it does not require compiling or bundling. As shown in the RFC, you'll be able to access all plugins and the editor core from one file thanks to importmaps. This means that unless you need to use TypeScript, you can skip the build step for custom plugins and run them directly in the browser. I've updated our demo repository to show this in action. In the new "browser demo", we create an editor with the most basic plugins and register a custom plugin that runs directly in the browser without any processing.

If you need to process your source code (e.g. because you're using TypeScript), we’ll provide the tools necessary to create CDN builds for custom plugins.

@filipsobol
Copy link
Member

@Kocal

Reducing the length of the webpack config is always nice, isn't it? 🙂

In our CMS app, we have many languages and therefore as many CKEditor translations .js files. We don't load every translations at once, but only the needed one, through a dynamic import:
[…]
Do you think this would be possible with the proposed solutions?

Dynamic import should work just the same.

Could translations accepts a Promise? Or should I wait my translation to be loaded before creating the Editor like this?

We’ll discuss this internally, but it's much more likely that you'll need to resolve the promise before creating the editor.

@filipsobol
Copy link
Member

filipsobol commented Dec 15, 2023

@urbanspr1nter

Option to not include UI components when importing packages. Right now whenever we consume a plugin, we not only get the Editing but also the UI classes within the plugin. Is there some way to avoid this? For instance, hard to take advantage of a potential smaller bundle size as we cannot shake this off on import.

It's not in the scope of this initiative, but we're aware of that limitation because it comes back like a boomerang. It’s possible it will be a follow-up after we clean up the installation methods. (cc: @Inviz)

Embedding SVG icons and CSS into the build is unnecessary - I really appreciate seeing the recognition in this concern. Again, another win on loading perf for integrators who need to have a small bundle size.

Unfortunately, SVG icons will still be part of the bundle (as inlined strings). We have looked into making them optional, but that would require introducing a breaking change to the existing installation methods, which we do not want to do, as the changes we are introducing are already major.
They may become optional in the future, but this will likely not happen as part of this initiative.

Most importantly, I would like to see documentation which directly reflects how we should really build from source. I've been successful at this in the past, but there are some gotchas that I keep forgetting to help with updating the documentation with.

We are in the process of restructuring and reworking all of our setup and installation documentation. I can already say that it will be much better.

I would like to have some clear way to avoid the dreaded duplicated-modules
error. For us, we have a strange way of building the source, but it
makes it hard to pull down other handy classes and utils and utilize
(impossible really)

With the ckeditor5 package containing everything, the duplicated-modules error will largely disappear, as you'll have one central entry point and one direct dependency instead of dozens of interdependent packages.

@filipsobol
Copy link
Member

@quicksketch

Thank you for sharing your concerns. We do our best to ensure that the changes we introduce are not disruptive to the CMS platforms and that you have reasonable migration paths.
From the beginning, we knew that because of the way DLLs work and the number of projects that depend on them, they needed to be treated differently than other installation methods. As a result, DLLs will have a longer deprecation window than other installation methods.
In addition, after discussing your concerns internally, we have decided to prepare an additional RFC specific to DLLs that will discuss our reasons for deprecating them, likely migration paths, and timelines. We will also use it to better understand how they are used by projects like yours.

What I'm reading here is that now after finally figuring out CKE5, pretty much every plugin that extends CKE5 is going to need a substantial effort to keep working (probably forking for pre and post this change).

The DLLs and the new package bundles can be generated from the same source code, so there's no need to make forks. We will add a new command to the Package generator to generate the new bundles, and the old commands will continue to work. It will be up to package developers to decide if they want to support only the new method or both. To ensure better interoperability, we’ll recommend releasing both.

From what I've seen in the CMS-world, the DLL builds are the only realistic compilation option currently, since any CMS with their own module/plugin system need to be able to enable CKEditor plugins without recompiling the main bundle.
[..]
Plain vanilla JS plugins should be possible to be loaded without any compilation at all. If the CKEditor5 global is eliminated I'm concerned that won't be possible.

Our new package bundles don’t require compilation or bundling. They also allow creating custom plugins that can be developed and used without bundling/compilation. I've updated our demo repository to show this in action. In the new "browser demo", we create an editor with the most basic plugins and register a custom plugin that runs directly in the browser without any processing.
All of this is possible without global CKEditor5 object.

@quicksketch
Copy link

quicksketch commented Dec 15, 2023

I've updated our demo repository to show this in action. In the new "browser demo", we create an editor with the most basic plugins and register a custom plugin that runs directly in the browser without any processing.
All of this is possible without global CKEditor5 object.

Thanks @filipsobol. The concept of browser importmaps is new to me but using the example repository it demonstrated clearly to me how imports can be done without using path structures within the plugin files. That would allow loading plugins across domains and directories as I was hoping for, and without compilation for the individual plugins.

In addition, after discussing your concerns internally, we have decided to prepare an additional RFC specific to DLLs that will discuss our reasons for deprecating them, likely migration paths, and timelines.

Perfect, thank you very much. The deprecation of DLLs as part of the 40.2.0 release notes without a clear replacement was alarming.

Overall, I think this new approach is much better, and it would be nice to not need special DLL distribution files. I still see the transition to be bumpy, but I'm relieved to see my primary concerns are already addressed.

@BrancuAlexandru
Copy link

This is actually really cool of you guys to do, thanks. I ran into extensibility issues this week with prebuilts, which are now entrenched into our codebase and now I have to bring up actual tehnical debt next time I talk to my superior.

@Witoso
Copy link
Member Author

Witoso commented May 21, 2024

@DeltekDavid could you create a separate issue for this? I would rather not hijack the whole thread. The last idea I have is that you're using npm and running into peerDeps problem: ckeditor5-react pulls some misaligned deps as you're using the nightly. The simple fix is to use npm install --legacy-peer-deps

@DeltekDavid
Copy link

@DeltekDavid could you create a separate issue for this? I would rather not hijack the whole thread. The last idea I have is that you're using npm and running into peerDeps problem: ckeditor5-react pulls some misaligned deps as you're using the nightly. The simple fix is to use npm install --legacy-peer-deps

Thank you @Witoso , I got the packages to install with --legacy-peer-deps. I then got a Context Watchdog error on running the app. I created a tech support ticket to not derail the thread :D Thanks

@dt-salvador-diaz
Copy link

I think there is an issue with the current nightly build because when I checkout https://github.com/ckeditor/new-installation-methods and run yarn install and yarn build I get the following error:

± % yarn build
vite v5.2.12 building for production...
✓ 14 modules transformed.
x Build failed in 134ms
error during build:
[vite]: Rollup failed to resolve import "@ckeditor/ckeditor5-core/dist/index.js" from "/Users/sdiaz/work/new-installation-methods/vite/.yarn/cache/@ckeditor-ckeditor5-adapter-ckfinder-npm-0.0.0-nightly-20240604.1-9e1f720ad9-e0b75b76bd.zip/node_modules/@ckeditor/ckeditor5-adapter-ckfinder/dist/index.js".
This is most likely unintended because it can break your application at runtime.
If you do want to externalize this module explicitly add it to
`build.rollupOptions.external`
    at viteWarn (file:///Users/sdiaz/work/new-installation-methods/vite/.yarn/__virtual__/vite-virtual-667b4c94a5/0/cache/vite-npm-5.2.12-2eaf45dfa7-908b8a0946.zip/node_modules/vite/dist/node/chunks/dep-BKbDVx1T.js:68818:27)
    at onRollupWarning (file:///Users/sdiaz/work/new-installation-methods/vite/.yarn/__virtual__/vite-virtual-667b4c94a5/0/cache/vite-npm-5.2.12-2eaf45dfa7-908b8a0946.zip/node_modules/vite/dist/node/chunks/dep-BKbDVx1T.js:68846:9)
    at onwarn (file:///Users/sdiaz/work/new-installation-methods/vite/.yarn/__virtual__/vite-virtual-667b4c94a5/0/cache/vite-npm-5.2.12-2eaf45dfa7-908b8a0946.zip/node_modules/vite/dist/node/chunks/dep-BKbDVx1T.js:68529:13)
    at file:///Users/sdiaz/work/new-installation-methods/vite/.yarn/cache/rollup-npm-4.18.0-9eadb97a09-54cde921e7.zip/node_modules/rollup/dist/es/shared/node-entry.js:18514:13
    at Object.logger [as onLog] (file:///Users/sdiaz/work/new-installation-methods/vite/.yarn/cache/rollup-npm-4.18.0-9eadb97a09-54cde921e7.zip/node_modules/rollup/dist/es/shared/node-entry.js:20162:9)
    at ModuleLoader.handleInvalidResolvedId (file:///Users/sdiaz/work/new-installation-methods/vite/.yarn/cache/rollup-npm-4.18.0-9eadb97a09-54cde921e7.zip/node_modules/rollup/dist/es/shared/node-entry.js:19104:26)
    at file:///Users/sdiaz/work/new-installation-methods/vite/.yarn/cache/rollup-npm-4.18.0-9eadb97a09-54cde921e7.zip/node_modules/rollup/dist/es/shared/node-entry.js:19062:26

At first I tried in my project (it's a private enterprise repo so I cannot share it, sorry), and I got similar errors (failed to resolve import "@ckeditor/ckeditor5-core/dist/index.js" in lots of ckeditor plugins), but I just retried and now vite build works, I don't understand why. There is still another problem when I try running my unit tests with vitest:

SyntaxError: Unexpected token 'export'
 ❯ internalCompileFunction node:internal/vm:73:18
 ❯ wrapSafe node:internal/modules/cjs/loader:1178:20
 ❯ Module._compile node:internal/modules/cjs/loader:1220:27
 ❯ Object.Module._extensions..js node:internal/modules/cjs/loader:1310:10
 ❯ Module.load node:internal/modules/cjs/loader:1119:32
 ❯ Function.Module._load node:internal/modules/cjs/loader:960:12
 ❯ ModuleWrap.<anonymous> node:internal/modules/esm/translators:169:29
 ❯ ModuleJob.run node:internal/modules/esm/module_job:194:25

This error originated in "tests/unit/specs/components/TextEditor/editor/BaseTextEditor.spec.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running.
Module /Users/sdiaz/work/hlx-webapp/node_modules/turndown/lib/turndown.browser.es.js:919 seems to be an ES Module but shipped in a CommonJS package. You might want to create an issue to the package "turndown" asking them to ship the file in .mjs extension or add "type": "module" in their package.json.

As a temporary workaround you can try to inline the package by updating your config:

// vitest.config.js
export default {
  test: {
    server: {
      deps: {
        inline: [
          "turndown"
        ]
      }
    }
  }
}

It turns out that turndown is a dependency from @ckeditor/ckeditor5-markdown-gfm, is there something you can do to fix this (make it so tests can be run without having to resort to server.deps.inline) ?

@filipsobol
Copy link
Member

filipsobol commented Jun 4, 2024

Thank you for reporting these issues @dt-salvador-diaz.

I just created a fresh clone of https://github.com/ckeditor/new-installation-methods, then ran yarn install && yarn build && yarn preview inside the vite folder (using Node 18 and Yarn Classic) and did not encounter the problem you describe.

Can you please share the output of the node -v and yarn -v commands, and the Vite and Vitest versions and config you use?


I also wrote a simple test using Vitest's Browser Mode, which (I think) should reveal obvious turndown issues, and had no problems.

import { expect, test } from 'vitest';
import { Editor } from './editor';

test( 'Uses Polish translation', () => {
  expect( Editor.defaultConfig.language ).toBe( 'pl' );
} );

Your Vite/Vitest configuration may also help us here.

Update: This and this might be related to the turndown issue.

Update 2: I was also able to run the below test without using Browser Mode, but with environment: 'jsdom' and environment: 'happy-dom' (the former required mocking ResizeObserver):

test( 'Can create a simple editor', async () => {
  const element = document.createElement( 'div' );
  element.innerHTML = '<p>Initial <b>content</b></p>';
  document.body.appendChild( element );

  const editor = await Editor.create( element );

  expect( editor.getData() ).toBe( 'Initial **content**' );
} );

@dt-salvador-diaz
Copy link

Thank you for reporting these issues @dt-salvador-diaz.

I just created a fresh clone of https://github.com/ckeditor/new-installation-methods, then ran yarn install && yarn build && yarn preview inside the vite folder (using Node 18 and Yarn Classic) and did not encounter the problem you describe.

Can you please share the output of the node -v and yarn -v commands, and the Vite and Vitest versions and config you use?

Thanks for taking a look at this, @filipsobol. Here's the output you asked for:

± % yarn -v
3.6.1

± % node -v
v18.17.1

I also wrote a simple test using Vitest's Browser Mode, which (I think) should reveal obvious turndown issues, and had no problems.

Your Vite/Vitest configuration may also help us here.

Update: This and this might be related to the turndown issue.

Update 2: I was also able to run the below test without using Browser Mode, but with environment: 'jsdom' and environment: 'happy-dom' (the former required mocking ResizeObserver):

We're using vite 5.1.6 and vitest 1.3.1, here's my vite/vitest config:

import { defineConfig, loadEnv } from 'vite';
import { resolve } from 'node:path';
import vue from '@vitejs/plugin-vue';
import svgLoader from 'vite-svg-loader';
import AutoImport from 'unplugin-auto-import/vite';
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';
import Components from 'unplugin-vue-components/vite';

export default defineConfig(({ mode }) => {
    const env = loadEnv(mode, process.cwd(), '');
    return {
        resolve: {
            alias: { '@': '/src' },
        },
        plugins: [
            vue(),
            svgLoader({ defaultImport: 'url' }),
            AutoImport({ resolvers: [ElementPlusResolver()] }),
            Components({
                dirs: [],
                resolvers: [ElementPlusResolver({ directives: !env?.TEST })],
            }),
        ],
        build: {
            rollupOptions: {
                input: {
                    main: resolve(__dirname, './index.html'),
                    external: resolve(__dirname, './external/index.html'),
                },
                output: {
                    manualChunks: id => {
                        if (id.includes('node_modules')) {
                            if (id.includes('ckeditor')) {
                                return 'vendor-ck';
                            }
                            if (id.includes('mammoth')) {
                                return 'vendor-mammoth';
                            }
                            if (id.includes('billboard') || id.includes('d3-')) {
                                return 'vendor-d3';
                            }
                            return 'vendor'; // all other package goes here
                        }
                    },
                },
            },
        },
        test: {
            environment: 'jsdom',
            globals: true,
            clearMocks: true,
            mockReset: true,
            restoreMocks: true,
            server: {
                deps: { inline: ['element-plus', 'graphql'] },
            },
        },
    };
});

@Witoso
Copy link
Member Author

Witoso commented Jun 26, 2024

"Alright," said the computer and settled into silence again. The two men fidgeted. The tension was unbearable.
"You're really not going to like it," observed Deep Thought.
"Tell us!"
"Alright," said Deep Thought. "The Answer to the Great Question..."
"Yes...!"
"Of Life, the Universe and Everything..." said Deep Thought.
"Yes...!"
"Is..." said Deep Thought, and paused.
"Yes...!"
"Is..."
"Yes...!!!...?"
"Forty-two," said Deep Thought, with infinite majesty and calm.

The Hitchiker's Guide to the Galaxy, Chapter 27

Our 9-month journey finishes with v42.0.0 🎉

Today, we released a brand-new version that solves issues which have haunted us for years. We achieved the goal we set for ourselves, the setup experience for CKEditor 5 is much simpler.

In addition to the technical migration, we aimed to smooth out the onboarding experience for new users. Our documentation has been restructured. We also released a new Builder, which allows users to explore our features and prepare their desired setup faster.

I want to thank all community members who helped us with tests and feedback ❤️

If you would like to migrate immediately (what we encourage 😁), check our migration guides. If you encounter any problems or have questions, open issues. We’d be happy to help.

Upwards and onwards, as they say. We have follow-ups listed, so stay tuned for further DX improvements this year.

@meetajhu
Copy link

meetajhu commented Aug 14, 2024

Using vite v5(node v18 LTS) with ckeditor 43.0.0 and @ckeditor/ckeditor5-react 9.0.0 i keep getting this error in vite server no exports main defined in package.json.

@Witoso
Copy link
Member Author

Witoso commented Aug 14, 2024

@meetajhu please create a separate issue with some reproducible sample for us to take a look at. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
support:2 An issue reported by a commercially licensed client. type:improvement This issue reports a possible enhancement of an existing feature.
Projects
None yet
Development

No branches or pull requests