ckeditor5-angular icon indicating copy to clipboard operation
ckeditor5-angular copied to clipboard

How to build editor from source?

Open ma2ciek opened this issue 5 years ago • 24 comments

Since the ng eject task for version 6 still isn't available, it's a more complicated task. Fortunately, the new CLI comes with builders, so we can use some predefined builder, which can have different build tasks and i.e. it can use different webpack config. Here comes the angular-builders package, which can append, replace or prepend specific part of webpack config to the angular's default one - see art without ejecting.

I gave it a try and the result was pretty good:

First, install additional dev dependencies (webpack loaders, CKEditor5 theme, CKEditor5 helpers and webpack builders):

npm i -D \
	css-loader \
	html-loader \
	raw-loader@1 \ 
	postcss-loader \
	style-loader \
	awesome-typescript-loader \
	angular2-template-loader \
	@ckeditor/ckeditor5-dev-utils \
	@ckeditor/ckeditor5-theme-lark \
	@angular-builders/custom-webpack \
	@angular-builders/dev-server

And dependencies (CKEditor5 core features):

npm i -S \
	@ckeditor/ckeditor5-editor-classic \
	@ckeditor/ckeditor5-essentials \
	@ckeditor/ckeditor5-basic-styles \
	@ckeditor/ckeditor5-heading \
	@ckeditor/ckeditor5-paragraph

Secondly, update your angular.json (and remember to fill with your project name):

"architect": {
	"build": {
		"builder": "@angular-builders/custom-webpack:browser",
			"options": {
				"customWebpackConfig": {
					"path": "./extra-webpack-config.js",
					"mergeStrategies": {
						"module.rules": "replace"
					}
            	},
				...
			}
		}
	},
	"serve": {
		"builder": "@angular-builders/dev-server:generic",
		"options": {
			"browserTarget": "your-project-name:build"
		},
		"configurations": {
			"production": {
				"browserTarget": "your-project-name:build:production"
			}
		}
	},
	...
}

And add simple webpack config under the ./extra-webpack-config.js path:

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

module.exports = {
  module: {
    rules: [
      {
        test: /\.svg$/,
        use: [ 'raw-loader' ]
      },
      {
        test: /ckeditor5-[^/\\]+[/\\]theme[/\\].+\.css/,
        use: [
          {
            loader: 'style-loader',
            options: {
              singleton: true
            }
          },
          {
            loader: 'postcss-loader',
            options: styles.getPostCssConfig( {
              themeImporter: {
                themePath: require.resolve( '@ckeditor/ckeditor5-theme-lark' )
              },
              minify: true
            } )
          }
        ]
      },
      {
        test: /\.ts$/,
        exclude: /\.(spec|e2e)\.ts$/,
        use: [
          'awesome-typescript-loader',
          'angular2-template-loader'
        ],
      },
      {
        test: /\.css/,
        exclude: /ckeditor5-[^/\\]+[/\\]theme[/\\].+\.css/,
        use: 'raw-loader'
      },
      {
        test: /\.html/,
        use: [
          'html-loader'
        ]
      }
    ]
  }
}

Then you can create your component that can use CKEditor5 plugins (app.component.ts):

import { Component } from '@angular/core';

import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
import Heading from '@ckeditor/ckeditor5-heading/src/heading';

@Component( {
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
} )
export class AppComponent {
  public Editor = ClassicEditor;

  public config = {
    plugins: [ Essentials, Paragraph, Bold, Italic, Heading ],
    toolbar: [ 'heading', '|', 'bold', 'italic', '|', 'undo', 'redo', ]
  };
}

And write a simple template (app.component.html):

<ckeditor [editor]="Editor" [config]="config"></ckeditor>

After that step, you'll be able to run ng serve / ng build.

There're a lot of steps, but for now, I don't think it could be done in some easier way. And I didn't check how tests work, but there is the jest builder too - https://github.com/meltedspark/angular-builders/tree/master/packages/jest.

The working example you can find here: https://github.com/ma2ciek/ckeditor5-angular-test/tree/t/26

ma2ciek avatar Aug 22 '18 08:08 ma2ciek

Hello, thanks for the documentation how to use it with a build from source. However, it does not work for me as expected. I am building the Angular application without the angular-cli package, which actually works very good. I am using webpack 4.12 to to build the application with Angular 6.0.6 right now.

Here the part of my webpack configuration I am using responsible for the editor integration:

...
        {
          test: /ckeditor5-[^\/]+\/theme\/icons\/[^\/]+\.svg$/,
          use: ['raw-loader']
        },
        {
          test: /ckeditor5-[^\/]+\/theme\/[\w-\/]+\.css$/,
          use: [
            {
              loader: 'style-loader',
              options: {
                singleton: true
              }
            },
            {
              loader: 'postcss-loader',
              options: styles.getPostCssConfig({
                themeImporter: {
                  themePath: require.resolve('@ckeditor/ckeditor5-theme-lark')
                },
                minify: true
              })
            },
          ]
        },
...

Please note that I am also using the plugin like this:

      new CKEditorWebpackPlugin({
        // See https://ckeditor.com/docs/ckeditor5/latest/features/ui-language.html
        language: 'en',
        additionalLanguages: 'all'
      }),

I also created a ckeditor.ts in my core module which looks the following:

import UploadAdapterPlugin from '@ckeditor/ckeditor5-adapter-ckfinder/src/uploadadapter'
import AutoformatPlugin from '@ckeditor/ckeditor5-autoformat/src/autoformat'
import BoldPlugin from '@ckeditor/ckeditor5-basic-styles/src/bold'
import ItalicPlugin from '@ckeditor/ckeditor5-basic-styles/src/italic'
import BlockQuotePlugin from '@ckeditor/ckeditor5-block-quote/src/blockquote'
import EasyImagePlugin from '@ckeditor/ckeditor5-easy-image/src/easyimage'
import ClassicEditorBase from '@ckeditor/ckeditor5-editor-classic/src/classiceditor'
import EssentialsPlugin from '@ckeditor/ckeditor5-essentials/src/essentials'
import HeadingPlugin from '@ckeditor/ckeditor5-heading/src/heading'
import ImagePlugin from '@ckeditor/ckeditor5-image/src/image'
import ImageCaptionPlugin from '@ckeditor/ckeditor5-image/src/imagecaption'
import ImageStylePlugin from '@ckeditor/ckeditor5-image/src/imagestyle'
import ImageToolbarPlugin from '@ckeditor/ckeditor5-image/src/imagetoolbar'
import ImageUploadPlugin from '@ckeditor/ckeditor5-image/src/imageupload'
import LinkPlugin from '@ckeditor/ckeditor5-link/src/link'
import ListPlugin from '@ckeditor/ckeditor5-list/src/list'
import ParagraphPlugin from '@ckeditor/ckeditor5-paragraph/src/paragraph'

export default class ClassicEditor extends ClassicEditorBase {}

ClassicEditor.builtinPlugins = [
  EssentialsPlugin,
  UploadAdapterPlugin,
  AutoformatPlugin,
  BoldPlugin,
  ItalicPlugin,
  BlockQuotePlugin,
  EasyImagePlugin,
  HeadingPlugin,
  ImagePlugin,
  ImageCaptionPlugin,
  ImageStylePlugin,
  ImageToolbarPlugin,
  ImageUploadPlugin,
  LinkPlugin,
  ListPlugin,
  ParagraphPlugin
]

ClassicEditor.defaultConfig = {
  toolbar: {
    items: [
      'heading',
      '|',
      'bold',
      'italic',
      'link',
      'bulletedList',
      'numberedList',
      'imageUpload',
      'blockQuote',
      'undo',
      'redo'
    ]
  },
  image: {
    toolbar: [
      'imageStyle:full',
      'imageStyle:side',
      '|',
      'imageTextAlternative'
    ]
  },
  language: 'en'
}

and a component like

import {Component} from '@angular/core'
import ClassicEditor from '@core/ckeditor'

@Component({
  selector: 'app-test',
  templateUrl: './app-test.component.html',
})
export class AppTestComponent {
 editor = ClassicEditor;
}

and finally I expected to see a result with the following markup:

<ckeditor [editor]="editor"></ckeditor>

The @core module obviously is importing the CKEditorModule and exporting it as well. The result is an error in my console with the following issue:

Class constructor ClassicEditor cannot be invoked without 'new'
    at new ClassicEditor (ckeditor.ts:19)
    at resolve (classiceditor.js:188)
    at new ZoneAwarePromise (zone.js:891)
    at Function.create (classiceditor.js:187)
    at CKEditorComponent.push../node_modules/@ckeditor/ckeditor5-angular/fesm5/ckeditor-ckeditor5-angular.js.CKEditorComponent.createEditor (ckeditor-ckeditor5-angular.js:187)
    at ckeditor-ckeditor5-angular.js:96
    at ZoneDelegate.innosabi_polyfills../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:388)
    at Zone.innosabi_polyfills../node_modules/zone.js/dist/zone.js.Zone.run (zone.js:138)
    at NgZone.innosabi_main../node_modules/@angular/core/fesm5/core.js.NgZone.runOutsideAngular (core.js:3613)
    at CKEditorComponent.push../node_modules/@ckeditor/ckeditor5-angular/fesm5/ckeditor-ckeditor5-angular.js.CKEditorComponent.ngAfterViewInit (ckeditor-ckeditor5-angular.js:95)

Using the way described in the first post, e.g. importong the original one and passing it with the options to the ckeditor component does not invoke any errors but gives me an empty editor without any markup. Any ideas what could be wrong?

ezintz avatar Sep 19 '18 12:09 ezintz

Hi, @ezintz!

Thanks for the detailed issue.

Do you use babel or some other tool that transpiles code? The error message (Class constructor ClassicEditor cannot be invoked without 'new') looks like something could be wrong there.

ma2ciek avatar Sep 19 '18 12:09 ma2ciek

You export the ClassicEditor class in the core/ckeditor.ts, so you should then import it in the component correctly. And you should import the Component from the angular's core.

Are you sure you use @core scoped packages so you can use @core/somePackage import? Probably you want to use e.g. import ClassicEditor from '../ckeditor'

I guess you have many errors there.

ma2ciek avatar Sep 19 '18 12:09 ma2ciek

You are right, it was just because I was trying quickly to hack some example code since my app is a bit larger. I will update the code snippets above..

Yes, I am sure it works even without scoped packages - meaning that I could also write 'core/path/to/my/module' - since the baseUrl is pointing to './src'. Yes, I see that there is actually something wrong but I am not sure why exactly.. that's why I decided to post a comment on this issue.

btw: I've updated the snippet above and no I am not using babel or anything else.

ezintz avatar Sep 19 '18 13:09 ezintz

ok :)

Does the same error throw now?

I'm also thinking about the TypeScript's transpilation. But it'd be weird. You could eventually try to use target: "ES6"

ma2ciek avatar Sep 19 '18 13:09 ma2ciek

It works with targeting ES6!

ezintz avatar Sep 19 '18 13:09 ezintz

Good to now that there's an issue there. Glad to help you :)

ma2ciek avatar Sep 19 '18 13:09 ma2ciek

Hi!

I'm facing similar issue in my project with Angular 5.2.11.

I have the following imports:

import * as ClassicEditor from '@ckeditor/ckeditor5-build-classic';
import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
import Underline from '@ckeditor/ckeditor5-basic-styles/src/underline';

And I'm using them like that:

public Editor = ClassicEditor;
public CKEConfig = {
  plugins: [Bold, Italic, Underline],
  toolbar: ['bold', 'italic', 'underline']
};

Then in my template it looks like:

<ckeditor [editor]="Editor"
          [config]="CKEConfig"></ckeditor>

And at compilation time (when Angular is served) it logs the following error in the console:

ckeditor-ckeditor5-angular.js:198 TypeError: Cannot read property 'getAttribute' of null
    at IconView._updateXMLContent (iconview.js:100)
    at IconView.render (iconview.js:76)
    at IconView.on (observablemixin.js:250)
    at IconView.fire (emittermixin.js:196)
    at IconView.(anonymous function) [as render] (webpack-internal:///./node_modules/@ckeditor/ckeditor5-utils/src/observablemixin.js:258:16)
    at ViewCollection.on (viewcollection.js:68)
    at ViewCollection.fire (emittermixin.js:196)
    at ViewCollection.add (collection.js:182)
    at ButtonView.render (buttonview.js:168)
    at ButtonView.on (observablemixin.js:250)

I guess, when I read what @ma2ciek wrote above, that I've something to do in the .angular-cli.json file (maybe in scripts/styles arrays or in stylePreprocessorOptions) but I don't know what. 🤔

maximelafarie avatar Sep 24 '18 13:09 maximelafarie

Hi, @maximelafarie.

I guess, when I read what @ma2ciek wrote above, that I've something to do in the .angular-cli.json file (maybe in scripts/styles arrays or in stylePreprocessorOptions) but I don't know what. 🤔

The problem is that you need to tell webpack how you want to handle SVG files. This guide was written for the angular@6 and I'm not sure if it fits the angular@5 environment. For the older versions the first step would be to call ng eject and then make some direct changes to the webpack.config.js, similarly to how it's done in the react integration guide.

If you don't want to call reject, you can still build the editor outside of the angular project and then include it to the angular one. It's described in the #6.

The second problem you're facing is the fact that you can't mix builds and source code. See installing plugins guide. If you want to extend the default setup for the ClassicEditor you can type import * as ClassicEditor from '@ckeditor/ckeditor5-build-classic/src/ckeditor'; instead of importing the whole bundle or import all plugins separately.

ma2ciek avatar Sep 24 '18 13:09 ma2ciek

Building my own ckeditor5 is working like a charm! Thank you so much @ma2ciek! 👌😚

maximelafarie avatar Sep 26 '18 10:09 maximelafarie

That's what I'm getting sadly.

CKEditorError@http://localhost:4200/vendor.js:92753:3 _setRanges/anyNewRange<@http://localhost:4200/vendor.js:75557:11 _setRanges@http://localhost:4200/vendor.js:75544:23 setTo@http://localhost:4200/vendor.js:75489:4 Selection@http://localhost:4200/vendor.js:75216:4 injectAndroidBackspaceMutationsHandling@http://localhost:4200/vendor.js:86081:25 init@http://localhost:4200/vendor.js:85277:3 ./node_modules/zone.js/dist/zone.js/</ZoneDelegate.prototype.invoke@http://localhost:4200/polyfills.js:2710:17 ./node_modules/zone.js/dist/zone.js/</Zone.prototype.run@http://localhost:4200/polyfills.js:2460:24 scheduleResolveOrReject/<@http://localhost:4200/polyfills.js:3194:29 ./node_modules/zone.js/dist/zone.js/</ZoneDelegate.prototype.invokeTask@http://localhost:4200/polyfills.js:2743:17 ./node_modules/zone.js/dist/zone.js/</Zone.prototype.runTask@http://localhost:4200/polyfills.js:2510:28 drainMicroTaskQueue@http://localhost:4200/polyfills.js:2917:25

webpreneur avatar Nov 07 '18 12:11 webpreneur

Hi, @webpreneur, could you post the error message as well?

ma2ciek avatar Nov 07 '18 12:11 ma2ciek

Dear @ma2ciek ,

I appreciate your immediate response,

To be honest, my exact challenge right now is in the following stackoverflow thread. (I'm not sure if this is the place where my issue'll be solved eventually :))

https://stackoverflow.com/questions/53188505/add-font-family-feature-to-ckeditor-toolbar-in-angular

PS.: That was the whole error message which I have found in the console.

Regards,

Zsolt

webpreneur avatar Nov 07 '18 13:11 webpreneur

I've looked at your SO question, but I think we can discuss it here as well (Or if you mind, you can create a new issue in this repository linking to this one). The fontFamily plugin isn't built-in into any of our builds. You should create a custom build or try to build the editor from the source like you do now.

What browser do you use? Maybe you could see if the error looks different on the other one? I'm asking because the above error doesn't mean much, it looks like some range error, but I can't tell which exactly looking at this stack trace.

The list of steps you did and the list of versions of CKEditor 5 packages you installed will also help to investigate that problem.

Kind regards, Maciek

ma2ciek avatar Nov 08 '18 09:11 ma2ciek

Dear @ma2ciek !

I answered on SO. Would you like me to post my reply here as well?

Regards,

Zsolt

webpreneur avatar Nov 12 '18 11:11 webpreneur

Hi again, @webpreneur!

I've replied to you on SO.

ma2ciek avatar Nov 14 '18 22:11 ma2ciek

Back to the topic, I've just found the https://github.com/manfredsteyer/ngx-build-plus. It seems to be an unofficial, but suggested solution (https://github.com/angular/angular-cli/issues/10618#issuecomment-425506339)

ma2ciek avatar Nov 15 '18 12:11 ma2ciek

I'm trying to build editor with ngx-build-plus but I'm facing issues with postcss-loader

ERROR in ./node_modules/@ckeditor/ckeditor5-ui/theme/components/button/button.css                                                                                                                
Module build failed (from ./node_modules/postcss-loader/src/index.js):
SyntaxError

(2:1) Unknown word

  1 | 
> 2 | var content = require("!!../../../../../postcss-loader/src/index.js??ref--19-1!./button.css");
    | ^
  3 | 
  4 | if(typeof content === 'string') content = [[module.id, content, '']];

I'm using webpack config from this issue and angular 7. Does anybody has working example? Thank You!

JurajKavka avatar Mar 25 '19 17:03 JurajKavka

Hi @JurajKavka,

I'll try to investigate this problem soon, maybe something in the build process has changed since the last time.

ma2ciek avatar Apr 16 '19 19:04 ma2ciek

I am not sure exactly what the issue is... but my first guess is that the CSS is not extracted correctly and webpack is trying to include it as Java-/TypeScript. Do you have any example configuration?

I have been switching back to CKEditor4 with Angular 7 and webpack without any Angular CLI, so I am not up to date. However this was happening, as far as I remember, when the extract css plugin was missing or wrong configured.

ezintz avatar Apr 17 '19 02:04 ezintz

I have followed your instructions on how to create a custom build with angular as shown above, however I have got several problems with the loaders. I am working with fuse template. Please if you have any suggestions on how to solve this issue let me know. Thank you !

ERROR in Module parse failed: Unexpected character '@' (1:0)

You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders

@import "src/@fuse/scss/fuse"; | | :host { ERROR in ./src/app/app.module.ts 29:0 Module parse failed: Unexpected character '@' (29:0) You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders | | @NgModule({ | declarations: [ | AppComponent, ERROR in ./src/styles.scss 2:0 Module parse failed: Unexpected character '@' (2:0) You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders | // Import Fuse core library @import "@fuse/scss/core"; | // Import app.theme.scss | @import "app/app.theme"; ERROR in ./src/hmr.ts 4:35 Module parse failed: Unexpected token (4:35) You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders | import { createNewHosts } from '@angularclass/hmr'; | export const hmrBootstrap = (module: any, bootstrap: () => Promise<NgModuleRef>) => { | let ngModule: NgModuleRef; | module.hot.accept(); ERROR in ./src/polyfills.ts 65:8 Module parse failed: Unexpected token (65:8) You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders | | // Add global to window, assigning the value of window itself. (window as any).global = window; |

BibiSebi avatar Sep 27 '19 10:09 BibiSebi

Hi @Sebi2909,

Sorry for the late response.

With the "mergeStrategies": { "module.rules": "replace" } loaders in the custom webpack configuration replaces the original angular ones and loader for scss files aren't set, probably that's why you face this issue. I'll check what is the default loader and settings for these files in the Angular ecosystem.

ma2ciek avatar Oct 21 '19 10:10 ma2ciek

2 years later ... has anyone made any progress on building from source in an angular project? I didn't manage to get the loading rules right for both angular and CKEditor

nadavsinai avatar Dec 26 '21 08:12 nadavsinai

@nadavsinai don't use CKEditor. Project is dead.

JurajKavka avatar Dec 26 '21 13:12 JurajKavka