CKeditor5 CKEditorError:ckeditor-duplicated-modules: Some CKEditor 5 modules are duplicated解决

RT 我们在使用 ekeditor的时候根据业务需要可能会使用两种不同类型的editor,比如Classsic,document,inline…等等

然后就会报错

CKEditorError:ckeditor-duplicated-modules: Some CKEditor 5 modules are duplicated

官网对于这个报错也提出了2种解决方法

原因是因为代码重复度太高了。
如果要在一个页面上加载两个不同的编辑器,则需要确保它们是一起构建的(一次)。这可以通过至少两种方式来实现:

将CKEditor 5从源代码直接集成到您的应用程序中。由于您只构建了一次应用程序,因此您使用的编辑器也将一起构建。
创建CKEditor 5的“超级版本”。您可以创建一个同时导出两个或多个的构建,而不是创建只导出一个编辑器的构建。

第一种就是挨个引用 直接把源码集成到我们项目中。我好奇的翻了一下github的源码。。我直接舍弃掉了

https://github.com/ckeditor/ckeditor5/tree/master/packages
这里不再过多赘述。我们主要讨论第二种

1.首先先克隆官方的代码

git clone -b stable https://github.com/ckeditor/ckeditor5-build-classic.git
npm i

然后把你另外的编辑器也给下载下来 (这里我们以doucument为例)具体类型的编辑器可以在上面的github地址里找到

npm i @ckeditor/ckeditor5-editor-decoupled   //这个是document

然后找到src/ckeditor.js

/**
 * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
 * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
 */

// The editor creator to use.
import ClassicEditorBase from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';

import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
import UploadAdapter from '@ckeditor/ckeditor5-adapter-ckfinder/src/uploadadapter';
import Autoformat from '@ckeditor/ckeditor5-autoformat/src/autoformat';
import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
import BlockQuote from '@ckeditor/ckeditor5-block-quote/src/blockquote';
import CKFinder from '@ckeditor/ckeditor5-ckfinder/src/ckfinder';
import EasyImage from '@ckeditor/ckeditor5-easy-image/src/easyimage';
import Heading from '@ckeditor/ckeditor5-heading/src/heading';
import Image from '@ckeditor/ckeditor5-image/src/image';
import ImageCaption from '@ckeditor/ckeditor5-image/src/imagecaption';
import ImageStyle from '@ckeditor/ckeditor5-image/src/imagestyle';
import ImageToolbar from '@ckeditor/ckeditor5-image/src/imagetoolbar';
import ImageUpload from '@ckeditor/ckeditor5-image/src/imageupload';
import Indent from '@ckeditor/ckeditor5-indent/src/indent';
import Link from '@ckeditor/ckeditor5-link/src/link';
import List from '@ckeditor/ckeditor5-list/src/list';
import MediaEmbed from '@ckeditor/ckeditor5-media-embed/src/mediaembed';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
import PasteFromOffice from '@ckeditor/ckeditor5-paste-from-office/src/pastefromoffice';
import Table from '@ckeditor/ckeditor5-table/src/table';
import TableToolbar from '@ckeditor/ckeditor5-table/src/tabletoolbar';
import TextTransformation from '@ckeditor/ckeditor5-typing/src/texttransformation';

export default class ClassicEditor extends ClassicEditorBase {}

// Plugins to include in the build.
ClassicEditor.builtinPlugins = [
	Essentials,
	UploadAdapter,
	Autoformat,
	Bold,
	Italic,
	BlockQuote,
	CKFinder,
	EasyImage,
	Heading,
	Image,
	ImageCaption,
	ImageStyle,
	ImageToolbar,
	ImageUpload,
	Indent,
	Link,
	List,
	MediaEmbed,
	Paragraph,
	PasteFromOffice,
	Table,
	TableToolbar,
	TextTransformation
];

// Editor configuration.
ClassicEditor.defaultConfig = {
	toolbar: {
		items: [
			'heading',
			'|',
			'bold',
			'italic',
			'link',
			'bulletedList',
			'numberedList',
			'|',
			'indent',
			'outdent',
			'|',
			'imageUpload',
			'blockQuote',
			'insertTable',
			'mediaEmbed',
			'undo',
			'redo'
		]
	},
	image: {
		toolbar: [
			'imageStyle:full',
			'imageStyle:side',
			'|',
			'imageTextAlternative'
		]
	},
	table: {
		contentToolbar: [
			'tableColumn',
			'tableRow',
			'mergeTableCells'
		]
	},
	// This value must be kept in sync with the language defined in webpack.config.js.
	language: 'en'
};

从这里我们能看到 export default class ClassicEditor extends ClassicEditorBase {} 他只抛出了这一个东西
所以我们把他重写一下

/**
 * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
 * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
 */

import ClassicEditorBase from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
import UploadAdapter from '@ckeditor/ckeditor5-adapter-ckfinder/src/uploadadapter';
import Autoformat from '@ckeditor/ckeditor5-autoformat/src/autoformat';
import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
import BlockQuote from '@ckeditor/ckeditor5-block-quote/src/blockquote';
import CKFinder from '@ckeditor/ckeditor5-ckfinder/src/ckfinder';
import EasyImage from '@ckeditor/ckeditor5-easy-image/src/easyimage';
import Heading from '@ckeditor/ckeditor5-heading/src/heading';
import Image from '@ckeditor/ckeditor5-image/src/image';
import ImageCaption from '@ckeditor/ckeditor5-image/src/imagecaption';
import ImageStyle from '@ckeditor/ckeditor5-image/src/imagestyle';
import ImageToolbar from '@ckeditor/ckeditor5-image/src/imagetoolbar';
import ImageUpload from '@ckeditor/ckeditor5-image/src/imageupload';
import Indent from '@ckeditor/ckeditor5-indent/src/indent';
import Link from '@ckeditor/ckeditor5-link/src/link';
import List from '@ckeditor/ckeditor5-list/src/list';
import MediaEmbed from '@ckeditor/ckeditor5-media-embed/src/mediaembed';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
import PasteFromOffice from '@ckeditor/ckeditor5-paste-from-office/src/pastefromoffice';
import Table from '@ckeditor/ckeditor5-table/src/table';
import TableToolbar from '@ckeditor/ckeditor5-table/src/tabletoolbar';
import TextTransformation from '@ckeditor/ckeditor5-typing/src/texttransformation';
import DecoupledEditorBase from '@ckeditor/ckeditor5-editor-decoupled/src/decouplededitor';
import Alignment from '@ckeditor/ckeditor5-alignment/src/alignment';
import FontSize from '@ckeditor/ckeditor5-font/src/fontsize';
import FontFamily from '@ckeditor/ckeditor5-font/src/fontfamily';
import FontColor from '@ckeditor/ckeditor5-font/src/fontcolor';
import FontBackgroundColor from '@ckeditor/ckeditor5-font/src/fontbackgroundcolor';
import Strikethrough from '@ckeditor/ckeditor5-basic-styles/src/strikethrough';
import Underline from '@ckeditor/ckeditor5-basic-styles/src/underline';
import IndentBlock from '@ckeditor/ckeditor5-indent/src/indentblock';





class ClassicEditor extends ClassicEditorBase {}
class DecoupledEditor extends DecoupledEditorBase { }
const plugins = [
	// FontSize,
	// FontFamily,
	// FontColor,
	// FontColor,
	// FontBackgroundColor,
	Essentials,
	UploadAdapter,
	Autoformat,
	Bold,
	Italic,
	BlockQuote,
	CKFinder,
	EasyImage,
	Heading,
	Image,
	ImageCaption,
	ImageStyle,
	ImageToolbar,
	ImageUpload,
	Indent,
	Link,
	List,
	MediaEmbed,
	Paragraph,
	PasteFromOffice,
	Table,
	TableToolbar,
	TextTransformation
]
const config = {
	toolbar: {
		items: [
			'heading',
			'|',
			'bold',
			'italic',
			'link',
			'bulletedList',
			'numberedList',
			'|',
			'indent',
			'outdent',
			'|',
			'imageUpload',
			'blockQuote',
			'insertTable',
			'mediaEmbed',
			'undo',
			'redo'
		]
	},
	image: {
		toolbar: [
			'imageStyle:full',
			'imageStyle:side',
			'|',
			'imageTextAlternative'
		]
	},
	table: {
		contentToolbar: [
			'tableColumn',
			'tableRow',
			'mergeTableCells'
		]
	},
	// This value must be kept in sync with the language defined in webpack.config.js.
	language: 'en'
};
const docplugins = [
	FontSize,
	FontFamily,
	FontColor,
	FontColor,
	FontBackgroundColor,
	Underline,
	IndentBlock,
	Strikethrough,
	Alignment,
	Essentials,
	UploadAdapter,
	Autoformat,
	Bold,
	Italic,
	BlockQuote,
	CKFinder,
	EasyImage,
	Heading,
	Image,
	ImageCaption,
	ImageStyle,
	ImageToolbar,
	ImageUpload,
	Indent,
	Link,
	List,
	MediaEmbed,
	Paragraph,
	PasteFromOffice,
	Table,
	TableToolbar,
	TextTransformation
]
const docconfig = {
	toolbar: {
		items: [
			'heading',
			'|',
			'fontfamily',
			'fontsize',
			'fontColor',
			'fontBackgroundColor',
			'|',
			'bold',
			'italic',
			'underline',
			'strikethrough',
			'|',
			'alignment',
			'|',
			'numberedList',
			'bulletedList',
			'|',
			'indent',
			'outdent',
			'|',
			'link',
			'blockquote',
			'imageUpload',
			'insertTable',
			'mediaEmbed',
			'|',
			'undo',
			'redo'
		]
	},
	image: {
		styles: [
			'full',
			'alignLeft',
			'alignRight'
		],
		toolbar: [
			'imageStyle:alignLeft',
			'imageStyle:full',
			'imageStyle:alignRight',
			'|',
			'imageTextAlternative'
		]
	},
	table: {
		contentToolbar: [
			'tableColumn',
			'tableRow',
			'mergeTableCells'
		]
	},
	// This value must be kept in sync with the language defined in webpack.config.js.
	language: 'en'
};
ClassicEditor.builtinPlugins = plugins
ClassicEditor.defaultConfig = config
DecoupledEditor.builtinPlugins = docplugins
DecoupledEditor.defaultConfig = docconfig
export default {
	ClassicEditor, DecoupledEditor
}

并且将webpack.config.js里的

library: 'ClassicEditor',
换个名字  //随便取
library: 'CKEDITOR',

注意点 在下载包的时候在package.json里检查一下包的版本 如果版本不一致也会报错 这里卡了我很久

都改完就可以打包了

npm run build

打完包可以在sample中的index中检查一下

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<title>CKEditor 5 – super build</title>
	<style>
		body {
			max-width: 800px;
			margin: 20px auto;
		}
	</style>
</head>
<body>

<h1>CKEditor 5 – super build</h1>

<div id="classic-editor">
	<h2>Sample</h2>

	<p>This is an instance of the <a href="https://ckeditor.com/docs/ckeditor5/latest/builds/guides/overview.html#classic-editor">classic editor build</a>.</p>
</div>

<div id="inline-editor">
	<p>
		123
	</p>
</div>

<script src="../build/ckeditor.js"></script>
<script>
	CKEDITOR.ClassicEditor
			.create( document.querySelector( '#classic-editor' ) )
			.catch( err => {
				console.error( err.stack );
			} );

	CKEDITOR.DecoupledEditor
			.create( document.querySelector( '#inline-editor' ) )
			.catch( err => {
				console.error( err.stack );
			} );
</script>

</body>
</html>

测试的时候在document用时候会没有导航栏 但是在项目中配置完就有 -,-
当一切准备就绪 就可以使用了
我把打包好的build直接把项目中的@ckeditor/ckeditor5-build-decoupled-document中的build给替换掉了

使用classic

<template>
  <div class="my-editor" :id="componentId">
    <textarea ref="textarea" v-model="currentValue" style="display: none;"></textarea>
  </div>
</template>

<script>
  /**
   * 富文本编辑器
   *
   */


  import UploadAdapter from './UploadAdapter'
  import CKEDITOR from '@ckeditor/ckeditor5-build-decoupled-document'
  import '@ckeditor/ckeditor5-build-classic/build/translations/zh-cn'
  const Toolbars = {
    simple: [
      'bold',
      'italic',
      'bulletedList',
      'numberedList'
    ],
    classic: [
      'heading',
      '|',
      'bold',
      'italic',
      'link',
      'bulletedList',
      'numberedList',
      'imageUpload',
      'blockQuote',
      'insertTable'
    ],
    all: [
      'heading',
      '|',
      'bold',
      'italic',
      'link',
      'bulletedList',
      'numberedList',
      'imageUpload',
      'blockQuote',
      'insertTable',
      'mediaEmbed',
      'undo',
      'redo'
    ]
  }

  /**
   * 上传的文件转换成base64
   * @private
   * @param loader
   * @return {Promise<any>}
   */
  function fileToBase64(loader) {
    return new Promise((resolve, reject) => {
      if (!window.FileReader) {
        return reject(new Error('浏览器不支持FileReader'))
      }
      const reader = new FileReader()
      reader.onload = function (e) {
        loader.uploadTotal = e.total;
        loader.uploaded = e.loaded;
        resolve({
          default: reader.result
        })
      }
      reader.onerror = function (e) {
        reject(e)
      }
      loader.file.then(file => {
        reader.readAsDataURL(file)
      })
    })
  }


  export default {
    props: {
      value: {
        type: String,
        default: ''
      },
      toolbar: {
        type: [String, Array],
        default: 'classic',
        validator(val) {
          return Array.isArray(val) || ['simple', 'classic', 'all'].includes(val)
        }
      },
      // ckeditor5 配置
      config: {
        type: Object,
        default() {
          return {
            language: 'zh-cn'
          }
        }
      },
      height: {
        type: Number
      },
      // 图片上传方法,需要返回Promise
      upload: {
        type: Function
      },
      readonly: Boolean,
      disabled: Boolean
    },
    data() {
      this.ckeditor = null
      this.styleElement = null
      return {
        currentValue: this.value
      }
    },
    computed: {
      ckeditorConfig() {
        return {
          toolbar: Toolbars[this.toolbar] || this.toolbar,
          ...this.config
        }
      },
      componentId() {
        return `my-editor-${this._uid}`
      }
    },
    watch: {
      value: {
        immediate: true,
        handler(val) {
          this.currentValue = val
        }
      },
      currentValue(val) {
        this.$emit('input', val)
        /**
         * 内容变化时触发
         * @event on-change
         * @param {string} val 新内容
         */
        this.$emit('change', val)
      },
      height: {
        immediate: true,
        handler(val) {
          val && this.setStyle(val)
        }
      },
      readonly(val) {
        if (this.ckeditor) {
          this.ckeditor.isReadOnly = val
        }
      },
      disabled(val) {
        if (this.ckeditor) {
          this.ckeditor.isReadOnly = val
        }
      }
    },
    methods: {
      init() {
        CKEDITOR.ClassicEditor.create(this.$refs.textarea, this.ckeditorConfig)
          .then(editor => {
            this.ckeditor = editor
            this.ckeditor.isReadOnly = this.readonly || this.disabled
            this.ckeditor.plugins.get('FileRepository').createUploadAdapter = loader => {
              return new UploadAdapter(loader, this.upload || fileToBase64)
            }
            this.bindEvents(editor)
          }).catch(e => {
          console.error('init CKEditor error', e)
        })
      },
      bindEvents(editor) {
        editor.model.document.on('change:data', () => {
          this.currentValue = this.getData()
        })
        editor.editing.view.document.on('focus', evt => {
          this.$emit('focus', evt, editor)
        })

        editor.editing.view.document.on('blur', evt => {
          this.$emit('blur', evt, editor)
        })
      },
      /**
       * 获取编辑器内容
       * @function getData
       * @return {*}
       */
      getData() {
        if (this.ckeditor) {
          return this.ckeditor.getData()
        }
        return null
      },
      /**
       * 设置编辑器内容
       * @function setData
       * @param {string} val 文本
       */
      setData(val) {
        this.currentValue = val
        this.ckeditor && this.ckeditor.setData(val)
      },
      setStyle(height) {
        // 由于 ckeditor 没有参数和接口调整编辑器的高度,这里采用在页面加载css来实现设置制定高度
        if (!this.styleElement) {
          this.styleElement = document.createElement('style')
          document.getElementsByTagName('head')[0].appendChild(this.styleElement)
        }
        this.styleElement.innerText = `#${this.componentId} .ck-content {height: ${height}px; }`
      }
    },
    mounted() {
      this.init()
    },
    beforeDestroy() {
      // 销毁样式元素
      if (this.styleElement) {
        this.styleElement.parentNode.removeChild(this.styleElement)
      }
      // 销毁 ckeditor
      this.ckeditor && this.ckeditor.destroy()
      this.ckeditor = null
    }
  }
</script>

使用 document

<template>
  <div class="document-editor">
    <div class="document-editor__toolbar">
    </div>
    <div class="document-editor__editable-container">
      <div class="document-editor__editable">
        <p>The initial editor data.</p>
      </div>
    </div>
  </div>

</template>

<script>
  import CKEDITOR from '@ckeditor/ckeditor5-build-decoupled-document'
  import '@ckeditor/ckeditor5-build-decoupled-document/build/translations/zh-cn'
  import UploadAdapter from './UploadAdapter'
  /**
   * 上传的文件转换成base64
   * @private
   * @param loader
   * @return {Promise<any>}
   */
  function fileToBase64(loader) {
    return new Promise((resolve, reject) => {
      if (!window.FileReader) {
        return reject(new Error('浏览器不支持FileReader'))
      }
      const reader = new FileReader()
      reader.onload = function (e) {
        loader.uploadTotal = e.total;
        loader.uploaded = e.loaded;
        resolve({
          default: reader.result
        })
      }
      reader.onerror = function (e) {
        reject(e)
      }
      loader.file.then(file => {
        reader.readAsDataURL(file)
      })
    })
  }

  export default {
    name: "ckeditor",
    props: {
      // 图片上传方法,需要返回Promise
      uploadImgHook: {
        type: Function,
        default() {
          return () => {
            console.error("undefined uploadImg Hook")
          }
        }
      }
    },
    data(){
      return {
        editor: null
      }
    },
    methods: {
      click(){
        console.log(this.editor.getData())
      },
      init(){
        CKEDITOR.DecoupledEditor
          .create( document.querySelector( '.document-editor__editable' ), {
            simple: [
              'bold',
              'italic',
              'bulletedList',
              'numberedList'
            ],
            toolbar: [
              'heading',
              '|',
              'fontfamily',
              'fontsize',
              'fontColor',
              'fontBackgroundColor',
              '|',
              'bold',
              'italic',
              'underline',
              'strikethrough',
              '|',
              'alignment',
              '|',
              'numberedList',
              'bulletedList',
              '|',
              'indent',
              'outdent',
              '|',
              'link',
              'blockquote',
              'imageUpload',
              'insertTable',
              '|',
              'undo',
              'redo'
            ],
            language: "zh-cn",
          } )
          .then( editor => {
            this.editor=editor
            editor.plugins.get('FileRepository').createUploadAdapter = loader => {
              //let val = editor.getData();
              return new UploadAdapter(loader, this.upload || fileToBase64)
            };
            const toolbarContainer = document.querySelector( '.document-editor__toolbar' );
            toolbarContainer.appendChild( editor.ui.view.toolbar.element );
          } )
          .catch( err => {
            console.error( err );
          } );
      }
    },
    mounted(){

      this.init()
    }
  }
</script>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值