女王控的博客

博客 Gatsby 插件改造

需求背景

针对本博客现有的 Gatsby 框架实现代码复制、代码实时查看编辑的功能

技术选型

以下都是采用最后一种方案,由于插件功能不满足,所以对其源码做出改动

代码复制

技术选型 语法 优点 缺点
项目代码自实现 未实现 定制化程度高
  1. 实现难度大;
  2. 拓展较差
gatsby-remark-code-buttons 不需特定语法,只要是代码块默认具有复制功能 已具有代码复制功能
  1. 有多复制一行的 bug;
  2. UI 不符合博客主题

代码实时查看编辑

技术选型 语法 优点 缺点 样例页面
iframe + gatsby-remark-embed-snippet <iframe src="/examples/snow.html" width="800" height="400"></iframe>
`embed:snow.html`
实现最为简单,容易出效果
  1. 没有实时编辑功能;
  2. 嵌入的代码往往需要将其写成一个 html 文件且一般情况下只支持静态页面的展示;
  3. 需要 iframe、embed 标签声明两次
下雪特效
iframe + code-editor.html 传参 <iframe src="/examples/code-editor.html?html=转义字符串&css=转义字符串&js=转义字符串 基本实现代码的实时查看编辑
  1. 嵌入的代码只支持静态的 html 页面;
  2. 需要明确拆分出 js、css、html 三个结构;
  3. 嵌入的 js、css、html 分别需要手动 escape 加密
CSS 世界四大盒尺寸
gatsby-remark-embedded-codesandbox [pms-example](embedded-codesandbox://gantt-component-optimization/pms-example) 借助 codesandbox 实现了较为丰富的代码实时查看编辑功能
  1. 借助第三方实现的功能,不够稳定;
  2. 不支持带目录的文件夹;
  3. 单次请求过大(即上传代码过多)会报请求体过大错误;
  4. 会上传 node_modules 等等大文件
需实现

核心逻辑

代码复制

修复复制多一行 bug

代码在这里

src/gatsby-browser.js

diff 复制代码
@@ -7,19 +7,9 @@ exports.onClientEntry = () => {
    el.innerHTML = str;
    document.body.appendChild(el);

-   const range = document.createRange();
-   range.selectNode(el);
-   window.getSelection().removeAllRanges();
-   window.getSelection().addRange(range);
-   document.execCommand(`copy`);
-   document.activeElement.blur();
-   setTimeout(() => {
-     document.getSelection().removeAllRanges();
-     document.body.removeChild(el);
-   }, 100);
+   el.select();
+   document.execCommand("copy");
+   document.body.removeChild(el);
    if (toasterId) {
      window.showClipboardToaster(toasterId);
    }

增加显示语言类型的功能;改变按钮文本显示逻辑;任何语言都可以显示复制按钮

主要针对原插件做出的一些 UI、功能上的适配,代码在这里

src/index.js

diff 复制代码
@@ -21,10 +21,6 @@ module.exports = function gatsbyRemarkCodeButtons(
    const actions = qs.parse(params);
    const { clipboard } = actions;

-   if (!language) {
-     return;
-   }
-
    if (clipboard === 'false') {
      delete actions['clipboard'];
    } else {
@@ -57,12 +53,12 @@ module.exports = function gatsbyRemarkCodeButtons(
            >
              <div
                class="${buttonClass}"
-               data-tooltip="${tooltipText}"
+               ${tooltipText ? `data-tooltip="${tooltipText}"` : ''}
              >
-               ${buttonText}${svgIcon}
+               ${[language, buttonText || svgIcon].filter((item) => item).join(' ')}
              </div>
            </div>
-           `.trim()
+         `.trim()
      };

      parent.children.splice(index, 0, buttonNode);

实现效果

js 复制代码
// 测试代码复制功能,这个代码块应该具有复制代码功能

代码实时查看编辑

代码在这里

递归遍历文件夹;增加忽略文件功能

原插件不支持目录的读取,这里拓展了目录下也能递归读取的功能,同时也实现了默认忽略文件的功能

js 复制代码
const DEFAULT_IGNORED_FILES = ['node_modules', 'package-lock.json', 'yarn.lock'];

const ignoredFilesSet = new Set(ignoredFiles);

const getAllFiles = (dirPath) =>
   fs.readdirSync(dirPath).reduce((acc, file) => {
      // 过滤文件立即跳出下一个
      if (ignoredFilesSet.has(file)) return acc;
      const relativePath = path.join(dirPath, file);
      const isDirectory = fs.statSync(relativePath).isDirectory();
      // 判断是目录继续递归遍历
      const additions = isDirectory ? getAllFiles(relativePath) : [relativePath.replace(`${directory}/`, '')];
      return [...acc, ...additions];
   }, []);

默认模式为静态服务器

因为 codesandbox 需要明确模板类型,这里默认为静态模板,否则静态文件不能正常运行

js 复制代码
const getFileExist = (fileList, filename = 'package.json') => {
   const found = fileList.filter((name) => name === filename);
   return found.length > null;
};

if (!getFileExist(folderFiles, 'sandbox.config.json')) {
   sandboxFiles.push({
      name: 'sandbox.config.json',
      content: '{ "template": "static" }'
   });
}

codesandbox 请求方式改为 post 异步化

原插件是基于 get 请求的,一旦上传文件过多就会报错,这里改为 post 请求就不再有请求体大小的限制,同时也要注意 post 的异步

js 复制代码
const convertNodeToEmbedded = async (node, params, options = {}) => {
   delete node.children;
   delete node.position;
   delete node.title;
   delete node.url;

   // merge the overriding options with the plugin one
   const mergedOptions = { ...embedOptions, ...options };
   const encodedEmbedOptions = queryString.stringify(mergedOptions);

   const { sandbox_id } = await fetch('https://codesandbox.io/api/v1/sandboxes/define?json=1', {
      method: 'POST',
      headers: {
         'Content-Type': 'application/json',
         Accept: 'application/json'
      },
      body: params
   }).then((x) => x.json());

   const sandboxUrl = `https://codesandbox.io/embed/${sandbox_id}?${encodedEmbedOptions}`;
   const embedded = getIframe(sandboxUrl);
   node.type = 'html';
   node.value = embedded;

   return node;
};

const nodes = [];
map(markdownAST, (node, index, parent) => {
   if (node.type === 'link' && node.url.startsWith(protocol)) {
      // split the url in base and query to allow user
      // to customise embedding options on a per-node basis
      const url = getUrlParts(node.url);
      // get all files in the folder and generate
      // the embeddeing parameters
      const dir = getDirectoryPath(url.base);
      const files = getFilesList(dir);
      const params = createParams(files);
      convertNodeToEmbedded(node, params, url.query);
      const currentNode = convertNodeToEmbedded(node, params, url.query);
      nodes.push(currentNode);
   }

   return node;
});

// 注意这里的异步化,必须等所有的请求成功响应才可继续遍历
await Promise.all(nodes);

实现效果

甘特图组件源码优化

总结

  1. 复制功能需考虑兼容性、复制性能,可参考JS 复制文字到剪切板的极简实现及扩展
  2. 代码实时查看编辑借助于第三方服务不够稳定,可自行搭建第三方服务或者参照 live-editor 自建本地编辑器,必要时具有分享功能

评论

阅读上一篇

TypeScript 练手测试
2021-11-24 16:36:17

阅读下一篇

甘特图组件源码优化
2021-11-15 11:48:33
0%