Ckeditor5: Introduce CKEditor 5 builds

Created on 23 Feb 2017  Â·  17Comments  Â·  Source: ckeditor/ckeditor5

Recently we talked about presets and now it's time to talk how specific builds will be maintained and delivered to the developers.

  • A build is a JS bundle of a specific setup of an editor class, plugins and theme.
  • The bundle is exposed in an UMD format (with CJS support!). It exports the editor class, with all the chosen plugins (see https://github.com/ckeditor/ckeditor5-core/issues/49 – plugins will be defined in Editor.bundledPlugins).
  • The bundle will be created based on an entry file (like we used to do it with Rollup) created based on a config file (also like we used to do it with Rollup – more about its format later).
  • Each build will be available on npm as a separate package and on GH as a separate repository. The repositories will be published on npm nearly "as is" in order to provide the same directory structure as npm packages and hence allow for easy switching between a package and GH repository.
  • We may decide to have more bundles of a single setup in one build – e.g. ES5 and ES6 versions (due to significant code size difference).
  • Builds will be added as ckeditor5 deps in order to be covered by our tests suite. This means that builds can have (and will have) manual and automated tests. Tests shouldn't be published on npm.
  • So far to bundle we'll use Webpack (because of https://github.com/rollup/rollup-plugin-node-resolve/issues/67 we can't use Rollup). We can later decide to switch to Rollup or allow using both.
  • Build repositories will be defined with minimal set of files (not as forks of ckeditor5 which we considered in the past). This is because it requires a different package.json and README.md and doesn't require most of the other files so there would be a lot of conflicts when merging from upstream. Also, we won't use gulp but a simple npm scripts.

Directory structure

ckeditor5-build-classic/
  build/
    ckeditor.js                     // or a specific name of this build?
    ckeditor.js.map
    ckeditor.es6.js
    ckeditor.es6.js.map
  .gitignore
  .npmignore
  build-config.js
  ckeditor.js
  package.json

Configuration

build-config.js:

'use strict';

module.exports = {
    destinationPath: './build/',
    editor: '@ckeditor/ckeditor5-editor-classic/src/classic'
    plugins: [
        '@ckeditor/ckeditor5-autoformat/src/autoformat',
        '@ckeditor/ckeditor5-basic-styles/src/bold',
        '@ckeditor/ckeditor5-basic-styles/src/italic',
        ...
    ],
    skin: '@ckeditor/ckeditor5-lark', // We support just one skin now, so this will be added later.
    moduleName: 'ClassicEditor' // The name of the global defined by the UMD format.
    // Predefined editor options.
    editorConfig: {
        toolbar: [ 'image', 'headings' ]
    }
};

This is very similar to what we have in https://github.com/ckeditor/ckeditor5-dev/tree/master/packages/ckeditor5-dev-bundler-rollup so we can perhaps use the same code to generate the entry file.

ckeditor.js

(as discussed here: https://github.com/ckeditor/ckeditor5-core/issues/49#issuecomment-282026658):

import ClassicEditorBase from '@ckeditor/ckeditor5-editor-classic/src/classic';
import AutoformatPlugin from '...';
import BoldPlugin from '...';
import ItalicPlugin from '...';

export default class ClassicEditor extends ClassicEditorBase {}


ClassicEditor.build = {
    plugins: [ AutoformatPlugin, BoldPlugin, ItalicPlugin ],
    config: {
        toolbar: [ 'image', 'headings' ]
    }
};

Note: The BaseEditor name must be quite unique to not conflict with moduleName. Or... let's it be moduleName + 'Base' :D.

The plugins names can be generated by taking the last piece of each plugin path and capitalizing it. I'm not sure if it's worth the work (we could simply use Plugin1, ...), but I think it may later be visible when debugging or dealing with these classes. Besides, we want to have the ckeditor.js committed to the repo.

Usage

Via <script> tag

npm i --save @ckeditor/ckeditor5-build-classic

And then:

<script src="node_modules/@ckeditor/ckeditor5-build-classic/build/ckeditor.js"></script>
<script>
    ClassicEditor.create( document.getElementById( 'editor' ) )
        .catch( err => console.error( err ) );
</script>

As CJS module (with Webpack, Browserify, etc.)

npm i --save @ckeditor/ckeditor5-build-classic

And then in your app:

const ClassicEditor = require( '@ckeditor/ckeditor5-build-classic' );

ClassicEditor.create( document.getElementById( 'editor' ) )
    .catch( err => console.error( err ) );

For this to work the main property in package.json must be defined. I'm not sure, though, to which version (ES5 or ES6 it should point). I think there's a separate property for ES6 main entry, isn't it?

Also, is it possible to require modules from inside of a package if main is defined?

When doing a deeper integration

npm i --save @ckeditor/ckeditor5-build-classic
const ClassicEditor = require( '@ckeditor/ckeditor5-build-classic/ckeditor' );

ClassicEditor.create( document.getElementById( 'editor' ) )
    .catch( err => console.error( err ) );

But in this case, you need to configure Webpack or another bundler so it's able to process CKEditor's files.

Rebuilding a build

git clone [email protected]:ckeditor/ckeditor5-build-classic.git
cd ckeditor5-build-classic
npm i

At this point, you can change the build configuration.

npm build

(or npm run build if there's no shortcut)

This will produce a new ckeditor.js entry file and create all the builds which we normally want to have (minified and non-minified, ES5 and ES6).

We may also later think about allowing to generate only the entry file or building based on your custom entry file. But this is future.

feature

All 17 comments

ckeditor.js
ckeditor.min.js

I know that we see this around among libraries. I have the impression that this comes from the old days, where the whole "source code" of a library was in a single file (jquery.js) and then they wanted to distribute a minified version alongside with it (jquery.min.js). Time changed, these projects evolved and they had to stick with that convention.

I think it has little sense for us to follow this approach. There is little-to-no benefit on this. I already see a lot of people misusing this and pointing to ckeditor.js, especially because that's what they always did with CKEditor so far. We're giving a disservice to developers by going that way.

If we really-really want to provide the unminified version, I would default to minified ckeditor.js and provide the unminified version alognside with it, naming it ckeditor.unmin.js (or "nomin" or whatever fits to it).

export default class ClassicEditor extends ClassicEditorBase

Can't we just extend the original ClassicEditor(Base) instead of forcing an additional inheritance level?

The bundle is exposed in an UMD format (with CJS support!)

True... more precisely with the AMD+Global+CJS (AMD with global always).

entry-file.js

That's a very strange name. We may have better alternatives:

  • classiceeditor.js (after all, it exports exactly this class name)
  • classic-editor-build.js (no match with the class name, but with the build name)
  • main.js (as this is the main module of the package)
  • ckeditor.js (a nice default name for all builds)

If we really-really want to provide the unminified version, I would default to minified ckeditor.js and provide the unminified version alognside with it, naming it ckeditor.unmin.js (or "nomin" or whatever fits to it).

OK, can be just the minified version. I was a bit uncertain about this too because we have the sourcemap there anyway

Can't we just extend the original ClassicEditor(Base) instead of forcing an additional inheritance level?

Hm... Yes and no.

We could do that if bundledPlugins were the only thing we need to set, but we also need to set the editor config. If we don't want to use inheritance it would need to be done like this:

import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classic';
import AutoformatPlugin from '...';
import BoldPlugin from '...';
import ItalicPlugin from '...';

ClassicEditor.bundledPlugins = [ AutoformatPlugin, BoldPlugin, ItalicPlugin ];
ClassicEditor.defaultConfig = configObjectTakenFromTheBuildConfig;

export default ClassicEditor;

There's one nasty edge case in which it's going to break badly – if someone used two entry points defined like this in his/her project (in the "When doing a deeper integration" scenario). If those entry points used the same editor class then the configs would conflict. Inheriting prevents that.

That's a very strange name. We may have better alternatives:

Good point. If someone doesn't now we're using Webpack/Rollup, then that name can be confusing.

So it could be ckeditor.js but then I'm a bit afraid that developers will think its the file they need to include in their projects... which is true, but in some use cases only (the "When doing a deeper integration"). OTOH, it'd be there super straightforward that /build/ckeditor.js is created based on /ckeditor.js so I like this idea.

There's one nasty edge case in which it's going to break badly – if someone used two entry points defined like this in his/her project (in the "When doing a deeper integration" scenario). If those entry points used the same editor class then the configs would conflict. Inheriting prevents that.

Very good point ;) Let it be a subclass then.

Btw, what about a single static property called build, like the following:

> ClassicEditor.build;
(Object) {
    plugins: [ AutoformatPlugin, BoldPlugin, ItalicPlugin ],
    config: < configObjectTakenFromTheBuildConfig >
}

The build name may fit better because our whole communication is about "builds" and some may not understand "bundle".

The build name may fit better because our whole communication is about "builds" and some may not understand "bundle".

Can be this way.

I'm considering renaming build.plugins to build.availablePlugins because there's also build.config.plugins. But it can be a shorter build.plugins too.

OK, all should be updated now.

I've been thinking about the ES5 build and it's very likely that we don't need to do much transpilation there. All the even partially supported browsers (like the mobile Safari) should support most of ES6 anyway. So I reported https://github.com/ckeditor/ckeditor5/issues/415 describing what's the easiest way to transpile only these features we really need.

If we'll decide to go this way we'll need to use Babel's preset-env + Babili. The last bit is something I'm not totally sure about but we can check it.

The advantage of course are smaller builds.

Should we think about a bold move and instead of going with the ckeditor.js+ckeditor.es6.js pair we would go ckeditor.js (es6)+ckeditor.es5.js?

The rational is that we would aim to keep the main file inline with the evergreen browsers while providing backwards compatibility with the es5 file.

This approach seems also to be more appropriate to the future, years from now, where ES6 may evolve on something with a different name. We'll be able to create a many "postfixed" versions as we wish, to provide backwards compatibility, while the main file remains modern.

Should we think about a bold move and instead of going with the ckeditor.js+ckeditor.es6.js pair we would go ckeditor.js (es6)+ckeditor.es5.js?

Yes, that's a move I was also considering. But it wasn't my main point. My main point was that Babel guys recommend using preset-env exactly to avoid the issue which we'd have with using preset-es5 – transpiling more than we need or maintaining our own preset which would satisfy the browsers that we support.

Also, if we'll support IE11, it will turn out that we need to transpile most of ES6 to ES5, so we'll change the configuration of that build to include IE11 support. However, some people may not need that particular browser but may need other ones which we support. So it will be easier for them to change that setting to exclude IE11 without checking which exactly ES6 features they can now use.

PS. I'm thinking more about ckeditor.js and ckeditor.compat.js (cause the latter doesn't have to pure ES5, at least for now). However, when we'll bring IE11 support it will become pretty much ES5. However again, in 5 years we may again drop IE11 and then that "es5" suffix would make less sense that a potential "compat". The "compat" mode could e.g. have some built-in support for some older browsers quirks, just like we have in CKE4. We could totally remove that code from the normal build to keep its size low and then "compat" would be about more than just a language.

Our PR for resolving symlinked packages is now merged in https://github.com/rollup/rollup-plugin-node-resolve, so we can start thinking about creating the rollup build as well.

Cool. I'll start from setting up an example build in github.com/ckeditor/ckeditor5-labs. However, I feel that we'll stay with Webpack for a while. I'd only consider switching to Rollup if it proves to give far smaller builds. But even then, we'd limit people to what Rollup allows and builds are meant to be a starting point for various integrations.

Was this page helpful?
0 / 5 - 0 ratings