-
Notifications
You must be signed in to change notification settings - Fork 4
Description
webpack 主文档的翻译和迁移工作基本接近尾声,感谢社区小伙伴的参与,才能让文档的翻译工作进行得如此迅速。
这里重点感谢 冯博 和 黄锦华 的积极参与与付出。
最近,有位细心的小伙伴在浏览文档时发现了锚点跳转异常的问题,这段时间我针对此问题着手进行了调研和解决。
先来描述下遇到的问题。
遇到的问题
打开 webpack 中文文档,随便找一篇文档介绍。比如直接访问概念 -> 入口,会发现页面空白。
具体异常如下:
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;
}如此修改后,发现浏览器能够跳转正常,问题得到了基本解决。
但是点击页面中的列表,又出现了新的问题。
新的问题
文中有大量引用链接,而锚点被改为了中文,链接无法识别也无法跳转。
有了新的问题,就要想新的解决方案。
和社区的小伙伴讨论了几种解决方案:
- 将英文文档作为中文文档的 submodules,然后做对应关系
- 全部改为中文,使用中文锚点
- 采用 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}以上为实际的输出效果。
如此修改又遇到了难题:
- webpack 本身的文档构建不能识别
{#}这种形式 - 即使能够识别,webpack 文档大约有 200 篇左右的文档,要手动修改?
我们开始针对文档站点的部署以及构建进行查看,然后思考如何对解决这两个问题。
魔改源码
先解决 webpack 构建不能识别 {#} 的问题。
翻看 webpack 文档的源码,你会发现 webpack 文档采用的是 remark 对 md 文件进行编译的,中间使用了大量的 remark 相关的插件。
简单介绍下
remark,remark是 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 表情,有特殊符号,空格等。
试用一下:
感觉还不错,基本上解决了问题。
lint 有一个最大的好处就是,可以批量修改文件。
具体所有操作,详见 docschina/webpack.js.org。
总结
难题总会有的,想办法解决就好了。
lint 可以不止于 lint。

