You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel Backpack自定义CKEditor构建报错:如何添加type="module"不修改源码?

问题:Laravel Backpack中CKEditor自定义构建脚本缺少type="module"属性的解决方法

更新

根据@jcastroa87的评论,我已补充ckeditor-init.js内容,更新ckeditor.js以匹配项目实际代码,并说明CKEditor构建的创建流程:

  • 通过CKEditor构建工具选择HTMLEditor预设,移除所有付费插件,保留默认工具栏,技术选型为VanillaJS,集成方式选自托管(npm),下载后将包里的main.js重命名为项目中的ckeditor.js。

问题背景

我正在Laravel Backpack的setupCreateOperation()方法中,为CKEditor字段配置自定义CKEditor5构建。按官方指引操作后,控制台报错:Uncaught SyntaxError: Cannot use import statement outside a module。

经排查,CKEditor构建工具生成的代码包含import语句(核心代码片段如下),但Backpack通过@basset指令生成的script标签缺少type="module"属性,导致浏览器无法识别模块语法:

// ckeditor.js 核心代码片段
import {
    ClassicEditor,
    AccessibilityHelp,
    Autoformat,
    AutoImage,
    AutoLink,
    Autosave,
    Base64UploadAdapter,
    Bold,
    Code,
    CodeBlock,
    Essentials,
    GeneralHtmlSupport,
    Heading,
    HtmlComment,
    HtmlEmbed,
    ImageBlock,
    ImageCaption,
    ImageInline,
    ImageInsert,
    ImageInsertViaUrl,
    ImageResize,
    ImageStyle,
    ImageTextAlternative,
    ImageToolbar,
    ImageUpload,
    Italic,
    Link,
    LinkImage,
    List,
    ListProperties,
    Markdown,
    MediaEmbed,
    Paragraph,
    SelectAll,
    ShowBlocks,
    SourceEditing,
    Table,
    TableCaption,
    TableCellProperties,
    TableColumnResize,
    TableProperties,
    TableToolbar,
    TextTransformation,
    Undo
} from 'ckeditor5';

import 'ckeditor5/ckeditor5.css';

const editorConfig = {
    'language': '{{ app()->getLocale() }}',
    toolbar: {
        items: [
            'undo',
            'redo',
            '|',
            'sourceEditing',
            'showBlocks',
            '|',
            'heading',
            '|',
            'bold',
            'italic',
            'code',
            '|',
            'link',
            'insertImage',
            'mediaEmbed',
            'insertTable',
            'codeBlock',
            'htmlEmbed',
            '|',
            'bulletedList',
            'numberedList'
        ],
        shouldNotGroupWhenFull: false
    },
    plugins: [
        AccessibilityHelp,
        Autoformat,
        AutoImage,
        AutoLink,
        Autosave,
        Base64UploadAdapter,
        Bold,
        Code,
        CodeBlock,
        Essentials,
        GeneralHtmlSupport,
        Heading,
        HtmlComment,
        HtmlEmbed,
        ImageBlock,
        ImageCaption,
        ImageInline,
        ImageInsert,
        ImageInsertViaUrl,
        ImageResize,
        ImageStyle,
        ImageTextAlternative,
        ImageToolbar,
        ImageUpload,
        Italic,
        Link,
        LinkImage,
        List,
        ListProperties,
        Markdown,
        MediaEmbed,
        Paragraph,
        SelectAll,
        ShowBlocks,
        SourceEditing,
        Table,
        TableCaption,
        TableCellProperties,
        TableColumnResize,
        TableProperties,
        TableToolbar,
        TextTransformation,
        Undo
    ],
    heading: {
        options: [
            {
                model: 'paragraph',
                title: 'Paragraph',
                class: 'ck-heading_paragraph'
            },
            {
                model: 'heading1',
                view: 'h1',
                title: 'Heading 1',
                class: 'ck-heading_heading1'
            },
            {
                model: 'heading2',
                view: 'h2',
                title: 'Heading 2',
                class: 'ck-heading_heading2'
            },
            {
                model: 'heading3',
                view: 'h3',
                title: 'Heading 3',
                class: 'ck-heading_heading3'
            },
            {
                model: 'heading4',
                view: 'h4',
                title: 'Heading 4',
                class: 'ck-heading_heading4'
            },
            {
                model: 'heading5',
                view: 'h5',
                title: 'Heading 5',
                class: 'ck-heading_heading5'
            },
            {
                model: 'heading6',
                view: 'h6',
                title: 'Heading 6',
                class: 'ck-heading_heading6'
            }
        ]
    },
    htmlSupport: {
        allow: [
            {
                name: /^.*$/,
                styles: true,
                attributes: true,
                classes: true
            }
        ]
    },
    image: {
        toolbar: [
            'toggleImageCaption',
            'imageTextAlternative',
            '|',
            'imageStyle:inline',
            'imageStyle:wrapText',
            'imageStyle:breakText',
            '|',
            'resizeImage'
        ]
    },
    initialData:
        'Test',
    link: {
        addTargetToExternalLinks: true,
        defaultProtocol: 'https://',
        decorators: {
            toggleDownloadable: {
                mode: 'manual',
                label: 'Downloadable',
                attributes: {
                    download: 'file'
                }
            }
        }
    },
    list: {
        properties: {
            styles: true,
            startIndex: true,
            reversed: true
        }
    },
    placeholder: 'Type or paste your content here!',
    table: {
        contentToolbar: ['tableColumn', 'tableRow', 'mergeTableCells', 'tableProperties', 'tableCellProperties']
    }
};

ckeditor-init.js内容:

async function bpFieldInitCustomCkeditorElement(element) {
    let ckeditorInstance = await ClassicEditor.create(element[0], editorConfig).catch(error => {
        console.error( error );
    });

    if (!ckeditorInstance) return;

    element.on('CrudField:delete', function (e) {
        ckeditorInstance.destroy();
    });

    ckeditorInstance.editing.view.document.on('layoutChanged', function (e) {
        element.trigger('change');
    });

    element.on('CrudField:disable', function (e) {
        ckeditorInstance.enableReadOnlyMode('CrudField');
    });

    element.on('CrudField:enable', function (e) {
        ckeditorInstance.disableReadOnlyMode('CrudField');
    });
}

Backpack生成的script标签:

<script src="http://0.0.0.0:5173/public/assets/js/ckeditor.js"></script>
<script src="http://0.0.0.0:5173/public/assets/js/ckeditor-init.js"></script>

环境版本:CKEditor5 43.1.0(npm安装)、backpack/crud 6.7.38、backpack/pro 2.2.17。

可行解决方案(无需修改Backpack源码)

方案1:自定义CKEditor字段视图

  1. 复制Backpack默认的CKEditor字段视图文件:
    从vendor/backpack/crud/src/resources/views/fields/ckeditor.blade.php复制到resources/views/vendor/backpack/crud/fields/ckeditor.blade.php
  2. 修改视图中的脚本加载部分,替换@basset调用为手动添加type="module"的script标签:
    <!-- 替换原有的@basset指令 -->
    <script type="module" src="{{ asset('assets/js/ckeditor/ckeditor.js') }}"></script>
    <script type="module" src="{{ asset('assets/js/ckeditor/ckeditor-init.js') }}"></script>
    
  3. 注意:如果editorConfig在ckeditor.js中,需确保ckeditor-init.js能正确访问该变量,或直接将配置移到init文件中。

方案2:通过Laravel Basset扩展添加属性

Laravel Basset支持为资源添加自定义属性,可在AppServiceProvider的boot方法中注册宏:

use Backpack\Basset\Facades\Basset;

public function boot()
{
    Basset::macro('module', function ($path) {
        return $this->add($path)->attribute('type', 'module');
    });
}

之后在自定义的CKEditor视图中使用@bassetModule(public_path('assets/js/ckeditor/ckeditor.js'))替代原有的@basset指令。

方案3:将CKEditor构建打包为非模块格式

修改CKEditor构建项目的webpack.config.js,将输出格式改为IIFE(立即执行函数表达式),无需type="module"属性:

  1. 打开CKEditor构建项目的webpack.config.js
  2. 修改output.library.type为'iife'
  3. 重新构建并替换项目中的ckeditor.js文件

方案4:动态修改DOM添加属性

在页面加载时,通过JavaScript为目标script标签动态添加type="module"属性:

document.addEventListener('DOMContentLoaded', function() {
    const ckeditorScripts = document.querySelectorAll('script[src*="ckeditor.js"]');
    ckeditorScripts.forEach(script => {
        script.setAttribute('type', 'module');
    });
});

注意:需确保此脚本在CKEditor加载前执行,否则仍可能报错。


内容的提问来源于stack exchange,提问作者René Korsgaard

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 01:05:57