使用 Markdoc 实现个性化用户引导
引言
我们最近发布了一个全新的优化版用户引导流程,旨在帮助开发者快速连接并查询PlanetScale数据库。
连接数据库的方式因应用使用的语言和框架而异。每种框架都有自己的细微差别,因此我们希望无论你的应用使用何种语言或框架,都能提供一条统一的路径让你轻松完成连接操作。
在用户引导页面中,你可以创建新的数据库以及选择框架。
本次引导功能的实现核心在于 **Markdoc**。本文将详细介绍,如何使用Markdoc构建我们的产品引导流程。
借助 Markdoc 实现更灵活的功能
Markdoc 是由 Stripe 创建的一种基于 Markdown 的语法,用于构建自定义文档站点。
我们之所以能够在 PlanetScale 快速迭代功能,是因为我们优先采用易用的工具,这些工具可以让公司内部的任何人都能够贡献内容。因此,使用 Markdown 和 GitHub 来设计产品引导流程对我们来说是非常合适的选择。
然而在开发过程中,我们迅速意识到需要更多互动性和个性化内容。纯静态的 Markdown 无法满足需求,这时我们开始尝试使用 **Markdoc**。
Markdoc 的语法是 Markdown 的超集,具体来说是基于 CommonMark 规范。这意味着你不仅可以使用你熟悉的 Markdown 来写内容,还可以扩展它以添加自定义属性、自定义标签,并使用函数和/或变量。
构建用户引导流程
示例代码解析
以下代码片段是用于用户引导流程中的部分 Markdown,用于展示连接教程。
rails credentials:edit --environment production
添加以下内容:
planetscale:
username: {% $user %}
host: {% $host %}
database: {% $database %}
password: {% $password %}
你会注意到我们在 Markdown 中使用了变量(例如 $user、$host、$database 和 $password)。每个用户引导路径都经过定制,旨在让用户按步骤完成操作更加简单。为选定的框架提供可直接复制粘贴的代码片段非常重要,这就是变量的重要性所在。
Markdoc中的变量
Markdoc 允许在运行时定制你的文档。我们通过使用变量将用户的凭证直接嵌入到内容中,而不是使用静态占位符值。
这类似于 Laravel 的 Blade 模板和 Ruby on Rails 的 ERB 模板。以下代码片段展示了如何设置变量以填充这些 Markdown 字段:
import React from 'react'
import { parse, renderers, transform } from '@markdoc/markdoc'
export default function Page() {
const config = {
variables: {
host: 'us-east.connect.psdb.cloud',
user: 'mpl0y3jv3a92h4qc4ufn',
database: 'beam',
password: 'pscale_pw_V8db13jnq5mrOWcGFn6GTs6AerDI7A0womsmnJ1qxOc',
ssl_ca: '/etc/ssl/certs/ca-certificates.crt'
}
}
const doc = `# Configure your application\n…`
const ast = parse(doc)
const content = transform(ast, config)
const children = renderers.react(content, React, {})
return <div>{children}</div>
}
Markdoc的节点功能
为了明确用户引导代码片段所在的文件,我们想扩展代码块,以支持额外的 file 属性。Markdoc 的 **Nodes**(节点)功能让你无需使用自定义语法即可定制文档的渲染方式。以下示例扩展了上一部分的代码片段,在代码块上方显示文件名。
import React from 'react'
import { parse, renderers, transform } from '@markdoc/markdoc'
export default function Page() {
const config = {
nodes: {
fence: Fence.scheme
},
variables: {
host: 'us-east.connect.psdb.cloud',
user: 'mpl0y3jv3a92h4qc4ufn',
database: 'beam',
password: 'pscale_pw_V8db13jnq5mrOWcGFn6GTs6AerDI7A0womsmnJ1qxOc',
ssl_ca: '/etc/ssl/certs/ca-certificates.crt'
}
}
const doc = `# Configure your application\n…`
const ast = parse(doc)
const content = transform(ast, config)
const children = renderers.react(content, React, { components: { Fence } })
return <div>{children}</div>
}
function Fence({ children, file, language }) {
return (
<div>
<div>{file}</div>
<pre>
<code className={`language-${language}`}>{children}</code>
</pre>
</div>
)
}
Fence.scheme = {
render: Fence.name,
children: ['pre', 'code'],
attributes: {
file: {
type: String
},
language: {
type: String
}
}
}
进一步的定制
一个常见问题是用户在安全连接到 PlanetScale 时难以选择合适的 SSL 证书。为了解决这个问题,我们构建了一个通用组件,根据用户的操作系统自动切换证书。
以下代码扩展了前一节的代码片段,通过检测用户的操作系统更改 ssl_ca 变量的值:
function connectPlatform(userAgent) {
userAgent = userAgent.toLowerCase()
switch (true) {
case /linux/.test(userAgent):
return 'linux'
case /mac/.test(userAgent):
return 'mac'
case /windows/.test(userAgent):
return 'windows'
default:
return 'ubuntu'
}
}
function connectSslCertificate(platform) {
switch (platform) {
case 'linux':
return '/etc/ssl/certs/ca-certificates.crt'
case 'mac':
return '/etc/ssl/cert.pem'
default:
return '/etc/ssl/certs/ca-certificates.crt'
}
}
除此之外,用户的开发环境常常与生产环境不同,因此我们还添加了一个选择器,允许用户自己选择证书。以下是最终代码:
import React, { createContext, useState } from 'react'
import { parse, renderers, transform } from '@markdoc/markdoc'
const Platform = createContext({ platform: null, setPlatform: () => {} })
export default function Page({ userAgent }) {
const initialPlatform = connectPlatform(userAgent)
const [platform, setPlatform] = useState(initialPlatform)
const sslCertificate = connectSslCertificate(platform)
const config = {
nodes: {
fence: Fence.scheme
},
variables: {
host: 'us-east.connect.psdb.cloud',
user: 'mpl0y3jv3a92h4qc4ufn',
password: 'pscale_pw_V8db13jnq5mrOWcGFn6GTs6AerDI7A0womsmnJ1qxOc',
ssl_ca: sslCertificate
}
}
const doc = `# Configure your application\n…`
const ast = parse(doc)
const content = transform(ast, config)
const children = renderers.react(content, React, { components: { Fence } })
return (
<Platform.Provider value={{ platform, setPlatform }}>
<div>{children}</div>
</Platform.Provider>
)
}
结果
我们对这次用户引导的效果感到非常满意,并根据早期数据,发现它对新用户起到了积极的作用。使用 Markdoc 不仅使开发过程既简单又直接,同时发现维护(如添加新框架)也非常方便。

关注公众号:程序新视界,一个让你软实力、硬技术同步提升的平台
除非注明,否则均为程序新视界原创文章,转载必须以链接形式标明本文链接