Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

记 webpack 中文文档的一次优化 #49

Open
QC-L opened this issue Jul 19, 2020 · 0 comments
Open

记 webpack 中文文档的一次优化 #49

QC-L opened this issue Jul 19, 2020 · 0 comments

Comments

@QC-L
Copy link
Owner

QC-L commented Jul 19, 2020

webpack 主文档的翻译和迁移工作基本接近尾声,感谢社区小伙伴的参与,才能让文档的翻译工作进行得如此迅速。

这里重点感谢 冯博 和 黄锦华 的积极参与与付出。

最近,有位细心的小伙伴在浏览文档时发现了锚点跳转异常的问题,这段时间我针对此问题着手进行了调研和解决。

先来描述下遇到的问题。

遇到的问题

打开 webpack 中文文档,随便找一篇文档介绍。比如直接访问概念 -> 入口,会发现页面空白。

image

具体异常如下:

DOMException: Failed to execute 'querySelector' on 'Document': '#%E5%85%A5%E5%8F%A3entry' is not a valid selector.

从错误中可以很直观的看出,我们向 querySelector 中传入了一个 id 选择器,其值为 #%E5%85%A5%E5%8F%A3entry

由于 id 中不能使用 %,所以报错,而出现 % 的原因是浏览器针对链接进行了 encode 操作,把「入口」转义成了 %E5%85%A5%E5%8F%A3

ps: 这个问题主要是对文中标题进行了中文翻译造成的。

初步解决方案

既然知道了是浏览器对链接进行转义造成的,那么我们针对使用到 querySelector 的地方在传参前转义即可。

webpack 文档的源码中查找 querySelector,最终在 src/components/Page/Page.jsx 中找到了元凶。

const hash = window.location.hash;
if (hash) {
  const element = document.querySelector(hash);
  if (element) {
    element.scrollIntoView();
  }
} else {
  document.documentElement.scrollTop = 0;
}

我们来优化下,防止浏览器转义中文字符:

const hash = window.location.hash;
if (hash) {
  const newHash = decodeURIComponent(hash);
  const element = document.querySelector(newHash);
  if (element) {
    element.scrollIntoView();
  }
} else {
  document.documentElement.scrollTop = 0;
}

如此修改后,发现浏览器能够跳转正常,问题得到了基本解决。

但是点击页面中的列表,又出现了新的问题。

新的问题

文中有大量引用链接,而锚点被改为了中文,链接无法识别也无法跳转

有了新的问题,就要想新的解决方案。

和社区的小伙伴讨论了几种解决方案:

  1. 将英文文档作为中文文档的 submodules,然后做对应关系
  2. 全部改为中文,使用中文锚点
  3. 采用 React 文档的形式,在标题后添加后缀(采用)

分别说下几种思路:

方案 1 的话,实现难度较高,并且对中英文文档要求较高,务必做到一一对应

方案 2 大量引用链接,逐个修改也会费时费力,并且无法做到傻瓜式修改

最终,采用了方案 3 的方式,可以做到与原文档无任何差别,并且保证了跳转。最重要的是,方便修改。

具体方案就是如下,在标题后加入{#锚点标题}

# Getting Start! {#getting-start}

Hello, Markdown

## Usage {#usage}

[link](https://docschina.org)

### 🔥Fire {#fire}

我是一行**加粗**的文字。

### Usage with configuration file {#usage-with-configuration-file}

以上为实际的输出效果。

如此修改又遇到了难题:

  1. webpack 本身的文档构建不能识别 {#} 这种形式
  2. 即使能够识别,webpack 文档大约有 200 篇左右的文档,要手动修改?

我们开始针对文档站点的部署以及构建进行查看,然后思考如何对解决这两个问题。

魔改源码

先解决 webpack 构建不能识别 {#} 的问题。

翻看 webpack 文档的源码,你会发现 webpack 文档采用的是 remark 对 md 文件进行编译的,中间使用了大量的 remark 相关的插件。

简单介绍下 remarkremark 是 markdown 的处理器。可以将 markdown 转换成你想要的效果。

在 webpack 文档站的 webpack.common.js 文件中有一个 mdPlugin 的属性:

const mdPlugins = [
  require('remark-slug'),
  [
    require('remark-custom-blockquotes'),
    {
      mapping: {
        'T>': 'tip',
        'W>': 'warning',
        '?>': 'todo'
      }
    }
  ],
  [
    require('remark-autolink-headings'),
    {
      behaviour: 'append'
    }
  ],
  [
    require('remark-responsive-tables'),
    {
      classnames: {
        title: 'title',
        description: 'description',
        content: 'content',
        mobile: 'mobile',
        desktop: 'desktop'
      }
    }
  ],
  require('remark-refractor')
];

通过断点调试的方式,可以发现在经过 remark-slug 插件后,文档的标题,就被处理成 html 中对应的 title 和 id 了。

因此,解决第一个问题的方式,就是将 remark-slug 魔改成,可以处理 {#} 的插件。

因此,我编写了 docschina-remark-slugger 插件,以解决 {#} 无法处理的问题。

除了 markdown 需要的编译需要修改之外,还需要针对文档站的左侧导航进行修改。

翻看源码,在 Page.jsx 中找到解析 title 和 id 的部分,进行了解析处理。

上述过程的处理方式,其实很简单,正则匹配到 {#} 中 # 后面的部分,将后面的部分设置为 id,然后将 {} 前的内容,设置为 title 即可。

奇技淫巧

解决了编译问题,接下来就是体力活了。

如何把这么多的文档都加上后缀?

最好省时省力,因为程序员都比较懒(比如我)。

修改文件最好的方式是通过 AST 修改,而修改 markdown 的 AST 网上已经存在需要现成的库。

刚刚上面提到的 remark 就属于这类库。

通过调研,我想到了一个非常取巧的方式。

采用 lint 的方式对源文档进行修改,再结合 git 可以实现对已翻译的文档进行后缀添加。

最后,经过一番调研,最后采用了 textlint 对 md 进行处理。

敲定了方案,开始翻阅文档思考如何解决。

我编写了测试文件 test.md

# Getting Start!

Hello, Markdown

## Usage

[link](https://docschina.org)

### 🔥Fire

我是一行**加粗**的文字。

### Usage with configuration file

这里将所有需要考虑的情况,都编写了进去。

如果想用 textlint 处理 md 文件,编写其对应的规则即可。

而我主要使用的是 lint 的 fix 功能。

因此,在编写规则时,还需编写其 fix 的处理方式。

textlint 的 rule 的实现如下:

const reporter = (context, options = {}) => {
    const {Syntax, RuleError, fixer, report, getSource} = context;
    return {
        [Syntax.Header](node) {
            let text = getSource(node); // Get text
            let match = /^.+(\s*\{#([a-z0-9\-_]+?)\}\s*)$/.exec(text);
            if (!match) {
                const index = text.length
                text = text.replace(/#/g, '')
                        .trim()
                        .replace(specials, '')
                        .replace(emoji(), '')
                        .replace(whitespace, '-')
                text = hyphenate(text)
                text = ` {#${text}}`
                const fix = fixer.insertTextAfter(node, text)
                const ruleError = new RuleError("Found bugs.", {
                    index, // padding of index
                    fix
                });
                report(node, ruleError);
            }
        }
    }
};

这里考虑了很多特殊情况,如标题中出现 emoji 表情,有特殊符号,空格等。

试用一下:

ezgif-7-88d007f4f63b

感觉还不错,基本上解决了问题。

lint 有一个最大的好处就是,可以批量修改文件。

具体所有操作,详见 docschina/webpack.js.org

总结

难题总会有的,想办法解决就好了。

lint 可以不止于 lint。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant