从 PrismJS 到 Better PrismJS 再到 MicroLighter:三步无痛迁移记

一个极好的特点十一是@11ty/eleventy-plugin-syntaxhighlight插件,通过 PrismJS 增加了构建时语法高亮支持。官方支持让110个网站无需担心基于JS的高亮导致客户端臃肿,以及常见的过度包含语言定义的失败模式。

但没有什么是完美的。

目录

官方插件自然集成,我开始用通常的三回溯标记语法使用它:

# The Usual Markdown Nonsense { #the-usual-nonsense }

And now for something entirely different:

```js
function yourCodeHere() { /* ... */ }
```

Etc., etc.

这方法非常有效,直到你想要更多控制权。那时,看似神奇的东西开始变得束缚。

最近的目标是增加行号。

读了部分提到PrismJS的插件代码后,我沮丧地合上了笔记本,无法劝说eleventy-plugin-syntaxhighlight输入行号。几天后离开键盘终于有了回报,因为我回想起配对短码路径生成的稳定每行分隔符。

这允许重用插件的主逻辑,并包裹着字符串变换以添加必要的样式钩子:

// From this site's Eleventy config file

// Because my esm conversion is still partial:
const { default: syntaxHighlight } = 
        await import("@11ty/eleventy-plugin-syntaxhighlight");
// ...
module.exports = async function(config) {
  // ...
  let syntaxHighlightOptions = {
    // For faster CSS selectors
    preAttributes: { highlighted: "highlighted" },
    codeAttributes: { highlighted: "highlighted" },
  };

  config.addPlugin(syntaxHighlight, 
                   syntaxHighlightOptions);

  config.addPairedShortcode("code",
                            function(content, 
                                    language="", 
                                    highlightLines="") {
    // The design of cssMin and addToBundle are outside 
    // the scope of this post.
    //
    // Suffice to say, we're inlining the contents 
    // of the file path passed as an argument to it.
    //
    addToBundle(
      this, 
      "css", 
      ``);

    let xformed = syntaxHighlight.pairedShortcode.call(
        this, 
        content, 
        language, 
        highlightLines, 
        syntaxHighlightOptions
    );

    // Wrap each line in `...`
    let _preamble = "
";
    let _end = "
"; let _content = ""; // Preamble and postfix might be different than // defaults above, so preserve them. let preambleEnd = xformed.indexOf(">", xformed.indexOf(""); _content = xformed.substring(preambleEnd, contentEnd); let parts = _content.split("
"); // Wrap each line in a stylable _content = parts.map((line) => { return `${line}`; }).join(`
`); return _preamble + _content + _end; }); // ... };

再加上一些CSS来帮我们算数,就是这样!,行号:

pre[highlighted] {
  display: block;
  overflow-x: auto;
  isolation: isolate;
  max-width: 100%;
  contain: content;

  & > code {
    width: 100%;
    text-wrap-mode: wrap;
    counter-reset: step;
    counter-increment: step 0;

    & .line::before {
      content: counter(step);
      counter-increment: step;
      width: 1.5rem;
      padding-right: 0.5rem;
      margin-right: 0.5rem;
      display: inline-block;
      text-align: right;
      color: var(--color-mid);
      border-right: 1px solid currentColor;
    }
  }
}

如果JavaScript方法看起来很熟悉,可能是因为它的技术去年我将11ty Bundler作为穷人路线代码分拆器投入使用时,已经概述了这一点允许每个需要JavaScript的短代码只懒惰地包含包含这些组件的页面:

// An `.eleventy.js` configuration file

module.exports = async function(config) {

  // Register some bundles that templates output.
  config.addBundle("js");
  // etc., ...

  // Needed to avoid Markdown double-processing.
  let removeNewlines = function(str) {
    return str.replace(/[\n\r]/g, "");
  }

  // Invoke shortcodes that `config.addBundle()` 
  // generates from within *other* shortcodes, 
  // allowing us to use the Bundler for duplicate code 
  // removal and hoisting dependencies to the optimal 
  // place in output templates.
  function addToBundle(scope, bundle, code) {
    config.getPairedShortcode(bundle).
            call(scope, removeNewlines(code));
  }

  config.addShortcode("someblock",
                      function(/* ... */) {
    addToBundle(this, "js", `
      
    `);
    // ...
  });
  // ...
};

11ty语法高亮技巧之所以有效,是因为调用输出时总有分隔符,通过HighlightPairedShortcode.虽然脆弱,但确实有效。通过Nunjucks配对短代码语法调用它比三重回溯更长,但并不痛苦:

# The Usual Markdown Nonsense { #the-usual-nonsense }

And now for something entirely different:

{% code "js" %}
function yourCodeHere() { /* ... */ }
{% endcode %}

Etc.

这个博客最近代码不多,但这几个月运行得还算不错。如果有人想看到它被整合成一个11ty插件,告诉我。

行号满足了我的渴望,但情况很不稳定;PrismJS 的标记不够理想,像高亮这样的功能从未工作过。我一直对其他解决方案感到好奇。

这时,我的同事戴夫·鲁珀特的非常酷微型打火机项目。如果你想了解更多关于背后超现代网络决策的信息,他有一个很棒的博客文章这揭示了原因和原因。

我一直在思考语法突出的替代方案,以及即将推出的基于11ty的多文档HTML幻灯片系统(暂名:“Faro”),正在重新设计{% code "..." %}在幕后使用其他设备似乎越来越有益。

为此,以下是我最终采用的整合MicroLighter的总体方法:

  1. 确保所有文件都被复制到输出目录。实际上情况比这复杂一些,因为我在开发者模式下为了速度,我避免复制,更倾向于使用符号链接——这个博客在Chromebook上重建时间不到六秒,甚至更少秒——但效果是一样的:
config.addPassthroughCopy({
  overwrite: false,
  "node_modules/microlighter/dist": "assets/microlighter",
});
  1. 用途去年文章的观点仅在需要时包含脚本并内联CSS。该系统包含三个组成部分:
    • 将核心网页组件JS加入捆绑包。
    • 内联主题 CSS(也通过捆绑器)。
    • 确保每种使用语言都能生成标签。
    这里需要一些调整来复制Web 组件的运行时语言别名(js此外javascript例如 )。我没有语言依赖的解决方案,但顶层语法预加载在我有限的使用中避免了网络停滞。
  2. 用短码处理内容逃逸。默认情况下,11ty 和 Nunjucks(我喜欢的模板引擎)会导出内容,替换<其中&lt;,例如,我们希望存在于
    ...
    阻挡。在周围标记意图以HTML形式出现在最终页面的情况下,这种方式就不太理想,比如网页组件的情况。绕过Nunjucks逃逸依赖于旧的env.filters.safe()以及env.filters.escape()黑客攻击,跳过Markdown逃脱则是markdown-it-ignore.这些工具结合起来,允许网页组件标记在其他处理过程中通过。

步骤#2是关键点。如果没有能够加载依赖、且仅在需要高亮时包含代码并引入语言定义的能力,我不会愿意采用客户端解决方案。

这个新短代码的依赖链有点笨重且针对特定网站,所以我不太愿意作为插件提供。如果有抱负的人感兴趣,我很乐意分享我的临时代码,并讨论如何让它变得更通用。

注释

  1. 我理解11ty's Build Awesome品牌重塑的品牌一致性论点。我非常喜欢扎克,他建立的可爱且支持的社区,以及Fonticons, Inc.团队通过Font Awesome、Web Awesome,现在还有Build Awesome所实施的OSS资助模式。我深深理解。当Build Awesome的众筹宣布时,我很早就投入资金支持这项工作,看到社区对项目的热情支持我感到无比欣慰。...以上都是前言,说明我不会无意中说出“打造超棒”这几个字。对我来说永远是110,我希望大家能原谅(或至少包容)我对风格指南的失误。
  2. 你可能会想“难道没有人会在2026年的捆绑包里包含所有PrismJS语言吧?”我会回答说,先生或女士,您并没有仔细看你所感受到的那堆页“有点慢”.不仅正确的依赖包含在“现代”前端中像鸡牙一样难,我个人在过去几年里诊断出近十起PrismJS语言定义过度包含的案例。也许未来会逐渐淡出语法突出错误,但那不是去年、上个月或昨天。
参与讨论