Drupal 11中寻找更好展示代码示例的方法:Flems代码沙箱集成与配置

Drupal 11中寻找更好展示代码示例的方法:Flems代码沙箱集成与配置

Drupal 11: Finding A Better Way To Display Code Examples

我运营这个网站大约有18年了,从我开始运营以来,我发布的代码基本都是相同的格式。代码通过 <code> 元素渲染到页面上,并且(大部分)使用 JavaScript 插件进行语法高亮显示。

我经常使用 CodepenJSFiddle 等网站,并且经常链接到这些网站以展示代码的使用示例。这些网站让我开始思考我网站上代码示例的静态性质。最近我编写了更多的前端代码,但静态的代码示例并不是展示这些功能实际运行效果的最佳方式。我可以(并且已经)上传功能运行的图片和动图,但这些图片的大小往往是代码示例的很多倍,只会让页面变得臃肿。

我真正想做的是允许将活动代码示例或代码沙箱嵌入到页面中。这将允许用户与代码示例进行交互,而不仅仅是静态地查看它们。显然,这对任何网站来说都是一个很有价值的学习工具。

我知道可以将 Codepen 示例嵌入到页面中,但这不仅需要高级订阅,还会造成代码与网站内容之间的脱节。我想要一个解决方案,让我能够在 Drupal 网站的后端编写文章和代码示例。

将代码示例托管在第三方网站上也存在一些风险,因为如果该网站离线,我网站上的所有代码示例都将无法工作。通过自托管,我可以改善编辑体验,还能确保一切正常运行。

现在我的网站需要某种代码沙箱,它可以用来展示简单的 JavaScript 和 CSS 代码,而无需依赖第三方供应商。因此,我进行了一些搜索,以找到适合代码的容器。

在本文中,我将研究一些代码沙箱,以及如何将它们集成到 Drupal 中。然后,我将特别关注一个代码沙箱,并允许将其嵌入到文章内容中。

A Quick Analysis Of Code Sandboxes

对允许(或者至少声称允许)某种形式自托管解决方案的代码沙箱进行(绝不是全面的)分析。这花了我好几个小时来挖掘搜索结果并找到示例。

**一、Playground Elements**

链接:https://github.com/google/playground-elements

由于是由谷歌创建的,我对这个沙箱抱有很高的期望,它似乎具备我所寻找的许多功能。该软件包声称可以在浏览器中直接运行用多种不同语言编写的代码,而无需后端服务器。

尽管 Playground Elements 声称易于安装,但我在让它运行起来方面遇到了很多麻烦。不仅如此,我找到的所有该系统看似在运行的示例似乎也都不起作用。我花了一个多小时试图让它在 Drupal 中运行,但最终放弃了,我只能假设它根本无法正常工作,因为我没有看到它正常运行的示例。

我可能不需要生活中再增加一个谷歌集成,而且由于该软件包似乎无法正常工作,我决定不使用它。我猜谷歌可能已经停止支持它,让它逐渐消亡了。

**二、Sandpack Client**

链接:https://sandpack.codesandbox.io/docs/advanced-usage/client 

这是 CodeSandbox 网站使用的沙箱,虽然它看起来很有前景,但整个系统似乎是围绕 React 组件构建的。经过一番试验,我意识到 sandpack-client 库只是系统的前端,你需要托管一个后端打包器才能让一切正常工作,而且这个后端打包器需要进行托管。

我并不想寻找一个需要后端系统才能工作的系统(我的意思是除了 Drupal 之外),所以这对我来说似乎有点太复杂了。

**三、Codeapi**

链接:https://codapi.org

这是一个有趣的项目,包含许多不同的 playground 来运行各种不同的语言,JavaScript 是系统自带的。其他语言需要后端系统才能正确处理。

虽然这个系统运行得相当不错,但我正在寻找一种可以展示 CSS 规则实际运行效果的工具,而这个编辑器目前还无法做到这一点。当你想在浏览器中运行任何类型的代码时,它是一个不错的小工具,我可能会在网站上为其他语言研究它。

**四、LiveCodes**

链接:https://livecodes.io

这个项目最初看起来像是一个托管产品,但实际上是一个不错的系统,它包含在一组小的 JavaScript 文件中。它不需要复杂的构建步骤或后端服务器来运行,因为它可以在浏览器中运行。唯一的缺点是,在嵌入时它会连接到 livecodes.io 域名,其中包含几个讨厌的分析 cookie,而且无法禁用。由于应用程序的创建方式,也不容易关闭这些 cookie。

该系统围绕 JavaScript、CSS 和 HTML 文件构建,并扩展到 React 和 Vue 等系统。LiveCodes 有一个独立的桌面应用程序,可用于快速进行应用程序原型设计。该系统的文档相当不错(偶尔有一些小的缺口),并详细描述了所有选项,并附有示例。

经过一番尝试,我设法让 LiveCodes 系统在 Drupal 中运行;代码被注入到 LiveCodes 包含文件中并展示出来。这个软件包几乎非常适合这个目的,并且可以使用一个相当简单的 JSON 数组进行大量配置。此外,它还有暗模式,这意味着它在这个网站上不会显得格格不入。

实际上它会“回传”以收集大部分资源(并且不遵守我网站的 cookie 策略)这一事实让我对这个工具相当反感。

**五、Flems**

链接:https://github.com/porsager/flems

尽管名字“有趣”,但这个项目感觉应该符合我的需求。将该工具包含到页面中需要将 flems.html 文件作为 JavaScript 包含文件添加,这看起来有点奇怪,但一切都能正常工作。更重要的是,它不会调用第三方网站来下载随机文件。

下载的 flems.html 文件大约有 140KB,有点大,但这是该插件的 JavaScript、HTML 和 CSS 文件的大小。Flems 运行不需要下载其他文件,只需要页面内的脚本来初始化它。

该软件包还支持运行 TypeScript 和 LiveScript 文件(这些文件在运行时编译),这也是一个优点。编辑器使用 CodeMirror,这意味着它具有良好的浏览器支持,因为 CodeMirror 是一个相当不错的 JavaScript 代码编辑器。

实际上,Flems 唯一真正的问题是它已经有几年没有更新了。近年来,为了解决安全问题,软件包进行了更新,但上一次真正的更新是在 2022 年。这不是一个大问题,但需要注意。

Flems 有一个完整的示例网站 https://flems.io,如果你有兴趣了解该软件包的全部潜力,可以去看看。

Drupal 很乐意将 flems.html 文件作为第三方库包含进来,但我确实需要解决 Flems 引起的一些小问题。没过多久,我就在 Drupal 控制器上让一切正常工作了,甚至配置了沙箱并将几个测试文件注入到沙箱中作为演示。

**六、Custom Build?**

另一个解决方案是构建一个涉及 iframe 的自定义系统,这是上述大多数系统使用的技术。HTML5 引入了 sandbox 属性,它提供了额外的限制,以进一步增强 iframe 沙箱的安全性,非常适合这个目的。

虽然这是可行的,但我想尽量避免这种方法,因为这意味着创建一个可能导致安全问题的系统,并将其嵌入到网站中。我更愿意选择一个现有的软件包,强调它有多好,甚至可能为它做出贡献。

Integrating Flems Into Drupal

最后,我选择了 Flems 作为在 Drupal 中嵌入代码沙箱的解决方案。它有点过时,但该系统完全符合我的需求,我可以根据需要对其进行分支和修改(我已经这样做了)。我还创建了 PR 以将这些更改贡献回原始软件包。

Flems 附带的说明非常简洁,但能完成工作。我们只需要将一个 HTML 文件作为 JavaScript 包含文件添加,然后就可以在页面中运行 Flems 系统。该工具存在几个配置选项,可以以不同的方式进行自定义,包括文件、链接、颜色、标签和只读模式。

为了将 Flems 集成到 Drupal 中,我从 git 仓库下载了该软件包的最新版本,然后构建了核心 flems.html 文件。为此,我只是在 Flems 目录中使用了 npm install 命令,然后是 npm run build。这会生成一个 dist 目录,重要的 flems.html 文件就位于该目录中。

我想在配置一个实体以允许通过 Drupal CMS 添加用户内容之前,先进行一个快速的概念验证。为此,我创建了一个小模块和一个单一的控制器,以便添加 Flems 正常工作所需的 HTML。

第一步是创建一个 *.libraries.yml 文件,以便我们可以注入 flems.html 文件。

  
    flems:
      header: true
      js:
        js/flems/dist/flems.html: { attributes: { type : 'text/javascript', charset : 'utf-8' } }
  
  

这个文件需要放在页面的头部(因此 header 属性设置为 true),这样我们就可以在页面上放置多个 Flems 实例。

有了这个设置后,我们现在可以添加一小段 HTML 和 Flems 运行所需的脚本,我使用 nowdoc PHP 创建了这些内容。这种语法本质上让我可以将一个大的多行字符串存储在一个变量中,而无需对其进行转义,这为我们提供了一种方便的机制,可以将示例脚本注入到页面中,而无需进行任何复杂的转义。

通过脚本创建的变量使用 markup Drupal 格式化器添加到控制器的渲染数组中。我们还传递了一些允许的 HTML 标签,这样我们在 nowdoc 中包含的标签在渲染时就不会被剥离。

  
    $flemsScript = <<<'EOD'
    <div id="container" class="flems-container"></div>
    <script>
      const container = document.getElementById("container");
      const flems = Flems(container, {
        files: [{
            name: 'app.js',
            type: 'script',
            content: `console.log(\`Hello from JS!\`);`,
        },
        {
            name: 'index.html',
            type: 'document',
            content: '<p>Test</p>',
        },
        {
            name: 'styles.css',
            type: 'style',
            content: 'p {color: blue; font-size: 30px}',
        },
        ],
        middle          : 50,
        selected        : 'app.js',
        color           : 'rgb(60, 60, 60)',
        theme           : 'material', // Can be material, none or default.
        resizeable      : true,
        editable        : true,
        toolbar         : true,
        fileTabs        : true,
        linkTabs        : true,
        shareButton     : false,
        reloadButton    : true,
        console         : true,
        autoReload      : true,
        autoReloadDelay : 1000,
        autoHeight      : false,
      });
    </script>
    EOD;

    $build['flems'] = [
      '#markup' => $flemsScript,
      '#allowed_tags' => ['script', 'div', 'p'],
    ];
  
  

上面示例中使用的选项是 Flems 文档中的一些基本选项,并进行了一些小的更改以自定义输出。有趣的是,代码输出将每秒刷新一次,这意味着当用户更改输入元素时,输出几乎会实时更新。

当我们加载控制器时,现在可以看到以下内容。

A running Flems instance inside a Drupal site. The image shows a console.log message being generated, along with test text that is styled blue.

你可以在页面 https://www.hashbangcode.com/code-sandbox-test-page 上看到控制器的实际运行情况,我在编写上述代码后对其进行了更改,以展示该工具更多的功能。上面的脚本并没有完全保留原始代码的空格,但传达了核心思想。随时可以试一试!

这运行得非常好,实际上我能够在沙箱中编辑代码,添加一个画布元素,在绿色背景上显示随机花朵。

A running Flems instance inside a Drupal site. The image shows JavaScript code running a canvas element that prints out some flowers in on a green background.

我将保留这个测试页面,作为快速确保插件在测试环境中正常工作的方法。

现在我已经确认一切正常工作,我需要更新 Drupal 以接受用户输入并为页面生成所需的输出。

Configuring Drupal Entities

我思考了如何将所需的数据添加到 Drupal 中,以便代码示例能够正确嵌入到内容中。我需要一种机制,允许我像添加代码块或图像一样,将代码示例添加到 CKEditor 字段中。

嵌入的代码示例需要与现有的 <code> 标签并存,这些标签本身对于展示代码和详细解释工作原理很有用,而不是总是使用完整的代码沙箱。

在研究了几个实体嵌入模块后,我决定最好的方法是使用 Paragraphs Entity Embed 模块。这是 Entity Embed 模块的一个实现,它允许将段落注入到 CKEditor 字段中。使用这个系统解决了我如果从头开发这个系统可能会遇到的许多问题,包括管理实体的 CKEditor 对话框。

Paragraphs 模块的添加让我可以轻松创建所需的实体,并将代码注入其中,而不必创建任何自定义字段或复杂的格式化器。我需要一组字段来存储所需的 JavaScript、HTML 和 CSS “文件”,然后可以将它们渲染到编辑器中。我还需要一个选择字段,将一个值注入到 selected 配置项中。

以下是创建的段落字段。

The paragraph field display for the code sandbox paragraph, showing the CSS, HTML, JavaScript, and Selected File fields.

段落的渲染模板对要在 Flems 界面中显示的文件做了一些假设。我想遵循 CodePen 的相同布局,即只存在 JavaScript、HTML 和 CSS 文件。Flems 可以进行大量扩展,以允许创建自定义文件并包含外部脚本。出于我的目的,我只希望有一个最小的代码沙箱来展示代码的实际运行效果,而不是一个完整的开发环境。

段落模板文件首先设置代码沙箱中标签的内容,将它们作为文件数组注入到模板中,并设置选定的标签。由于文件总是命名为 app.jsindex.htmlstyles.css,所以我们可以将文件设置为默认值。

以下是段落文件中的相关部分。

  
    {% set jsValue = paragraph.field_javascript.0.value|replace({'\`': '\\`'}) %}
    {% set htmlValue = paragraph.field_html.0.value|replace({'\`': '\\`'}) %}
    {% set cssValue = paragraph.field_css.0.value|replace({'\`': '\\`'}) %}

    <div id="container-{{ paragraph.id() }}" class="flems-container"></div>

    {% set selected = (paragraph.field_selected_file.0.value|raw == 'html') ? 'index.html' : (paragraph.field_selected_file.0.value|raw == 'css') ? 'styles.css' : '.js' %}
    <script>
      const container{{ paragraph.id() }} = document.getElementById("container-{{ paragraph.id() }}");
      const flems{{ paragraph.id() }} = Flems(container{{ paragraph.id() }}, {
        files: [
            {% if jsValue %}

            name: 'app.js',
            type: 'script',
            content: `{{ jsValue|raw }}`,
          },
            {% endif %}
            {% if htmlValue %}
          {
            name: 'index.html',
            type: 'document',
            content: `{{ htmlValue|raw }}`,
          },
            {% endif %}
            {% if cssValue %}
          {
            name: 'styles.css',
            type: 'style',
            content: `{{ cssValue|raw }}`,
          },
            {% endif %}
        ],
        middle: 50,
        selected: '{{ selected }}',
        color: 'rgb(60, 60, 60)',
        theme: 'material',
        shareButton: false,
        autoReloadDelay: 1000,
      });
    </script>
  
  

我需要在这里使用 raw 过滤器,以防止代码被转义。毕竟,目的是允许代码在这里运行,所以转义它只会破坏代码。如果你发现这里有任何明显的安全问题,请告诉我。

这里我没有包括一些细节,特别是关于权限的细节,但这展示了模板的基本用法。关于权限,目前我只允许网站管理员访问这个嵌入功能。我当然不打算让匿名用户嵌入沙箱,因为它缺乏安全性和无限循环检测。

使用这个系统,可以通过 CKEditor 字段将多个代码沙箱注入到页面中。由于我使用了 Paragraphs Entity Embed 模块,一旦创建了代码沙箱,我还可以在页面之间共享它们。

Conclusion

这个系统运行得非常好。以下是一个支持 Flems 的嵌入段落示例。代码示例展示了画布 API 中贝塞尔曲线的不同点是如何放置的。你可以在 JavaScript 文件中调整一些参数,查看输出的更新,输出每秒更新一次。

联系我们

提供基于Drupal的门户网站、电子商务网站、移动应用开发及托管服务

长按加微信
长风云微信
长按关注公众号
长风云公众号