This commit is contained in:
parent
ac55a0c6a2
commit
e9c32c4118
229
docs/dev/frontend/docusaurus插件开发.md
Normal file
229
docs/dev/frontend/docusaurus插件开发.md
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
# docusaurus 的插件开发与自定义主题
|
||||||
|
|
||||||
|
我为了本博客的一些需求,开发了一些 docusaurus 的插件,这里记录一下开发过程。其中踩到了一些坑,折腾了我好几天,结果回过头来一看是那么简单,希望对你能有所帮助。
|
||||||
|
|
||||||
|
## 创建插件
|
||||||
|
|
||||||
|
你需要创建一个类似下面的 npm 包:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "docusaurus-plugin-docs-info",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "src/index.ts",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@docusaurus/core": "^3.1.1",
|
||||||
|
"dayjs": "^1.11.10",
|
||||||
|
"reading-time": "^1.5.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
目录结构就像这样:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
其中源代码放在 src 目录下,theme 是相关主题,components 是组件
|
||||||
|
|
||||||
|
## 引用插件
|
||||||
|
|
||||||
|
创建完成插件后可以使用下面的方式进行引用
|
||||||
|
|
||||||
|
### 本地路径
|
||||||
|
|
||||||
|
可以通过本地相对路径来进行引用
|
||||||
|
|
||||||
|
```js
|
||||||
|
plugins: [
|
||||||
|
["./packages/docusaurus-plugin-docs-info", {}]
|
||||||
|
],
|
||||||
|
```
|
||||||
|
|
||||||
|
### npm 包
|
||||||
|
|
||||||
|
也可以使用安装 npm 包的形式来引用
|
||||||
|
|
||||||
|
首先需要执行:`npm i ./packages/docusaurus-plugin-content-docs-ex`来安装包,然后再使用下方的配置进行配置
|
||||||
|
|
||||||
|
```json
|
||||||
|
plugins: [
|
||||||
|
["docusaurus-plugin-docs-info", {}]
|
||||||
|
],
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
你可以看官方文档了解更多:[Plugin Method References](https://www.docusaurus.cn/docs/api/plugin-methods),官方主要分为以下四个部分:
|
||||||
|
|
||||||
|
- Lifecycle APIs:生命周期 API,当到达构建的某些阶段,docusaurus 会调用插件的生命周期 API,你可以在这些 API 中做一些事情,比如修改配置,添加一些内容等。
|
||||||
|
- Extending infrastructure:扩展基础设施
|
||||||
|
- I18n lifecycles:i18n 生命周期,一些 i18n 翻译相关的内容
|
||||||
|
- Static methods:静态方法,主要是校验选项和校验主题两个方法
|
||||||
|
|
||||||
|
这里我简单的介绍一些常用的:
|
||||||
|
|
||||||
|
### loadContent
|
||||||
|
|
||||||
|
这个 API 可以用来预加载一些内容,比如读取文件,将某些数据预加载好,这样在后续的 API 中就可以使用这些内容了。
|
||||||
|
例如官方的`docusaurus-plugin-content-docs`插件就是在此预加载好所有的文档数据,然后在`contentLoaded`中再添加路由的。
|
||||||
|
|
||||||
|
### contentLoaded
|
||||||
|
|
||||||
|
这个 API 有两个参数,一个是 content,一个是 actions,content 就是 loadContent 返回的内容,
|
||||||
|
actions 里面包含了`addRoute`、`createData`、`setGlobalData`,3 个方法,
|
||||||
|
可以通过这三个方法添加数据和路由,这样就可以自定义一些页面了。
|
||||||
|
|
||||||
|
### getThemePath
|
||||||
|
|
||||||
|
获取主题路径,在`contentLoaded`添加路由时,会要求填写一个组件:这里的组件填写的是一个字符串,你可以用`@site/`前缀表示主路径下的组件,也可以使用`@theme/`前缀表示`getThemePath`返回的路径下的组件。
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default function (context: LoadContext, options: any): Plugin {
|
||||||
|
const themePath = path.resolve(__dirname, "./theme");
|
||||||
|
return {
|
||||||
|
name: "docusaurus-plugin-docs-info",
|
||||||
|
getThemePath() {
|
||||||
|
return themePath;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// 添加路由
|
||||||
|
addRoute({
|
||||||
|
path: "/timeline",
|
||||||
|
component: "@theme/Timeline",
|
||||||
|
modules: {
|
||||||
|
articles: pageData,
|
||||||
|
},
|
||||||
|
exact: true,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### injectHtmlTags
|
||||||
|
|
||||||
|
在 body/head 中注入一些标签,比如添加一些统计代码,广告代码等。
|
||||||
|
|
||||||
|
### postBuild
|
||||||
|
|
||||||
|
构建完成时调用,会有生成路由的信息,可以在这里做一些事情,比如生成 sitemap, rss 等。
|
||||||
|
|
||||||
|
### validateOptions
|
||||||
|
|
||||||
|
校验选项,可以在这里校验一些配置是否正确。
|
||||||
|
|
||||||
|
## Hooks
|
||||||
|
|
||||||
|
除了上面的 API 之外,还有一些 hooks 可以使用,很多都是官方内部的,文档中并没有说明,也是我翻阅源码看到的,这里我列举一些我用到的:
|
||||||
|
|
||||||
|
### useGlobalData/PluginData
|
||||||
|
|
||||||
|
用于获取在`contentLoaded`中设置的全局数据与插件数据,可以在组件中使用。
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import useGlobalData, {
|
||||||
|
usePluginData,
|
||||||
|
useAllPluginInstancesData,
|
||||||
|
} from "@docusaurus/useGlobalData";
|
||||||
|
```
|
||||||
|
|
||||||
|
### useDoc
|
||||||
|
|
||||||
|
获取文档数据,可以获取到文档的标题、内容、路径等。
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { useDoc } from "@docusaurus/theme-common/internal";
|
||||||
|
```
|
||||||
|
|
||||||
|
### useColorMode
|
||||||
|
|
||||||
|
获取颜色模式,例如黑夜/白天模式。
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { useColorMode } from "@docusaurus/theme-common";
|
||||||
|
```
|
||||||
|
|
||||||
|
## 自定义主题
|
||||||
|
|
||||||
|
如果你想要自定义你自己的主题,你可以使用`npm run swizzle --typescript`命令,这样可以让你替换插件的默认组件,来自定义你自己的样式。
|
||||||
|
|
||||||
|
官方文档:[Swizzling | Docusaurus](https://docusaurus.io/docs/swizzling)
|
||||||
|
|
||||||
|
例如我就包装了原版的官方 docs 插件,然后自定义了主题,使我的博客 docs 稳定支持了创建时间与阅读时间。
|
||||||
|
|
||||||
|
## 使用包装器模式重制官方 docs 插件
|
||||||
|
|
||||||
|
虽然使用其它模式,例如再新写一个插件,也可以实现,但是使用包装器模式,可以更好的保持原有的插件的功能,只是在原有的基础上添加一些新的功能。
|
||||||
|
|
||||||
|
此处需要注意一个坑,如果使用包装器模式需要在`docusaurus.config.ts`中将原本的 docs 插件关闭掉,否则会产生冲突。
|
||||||
|
|
||||||
|
```ts
|
||||||
|
presets: [
|
||||||
|
[
|
||||||
|
"classic",
|
||||||
|
{
|
||||||
|
docs: false,
|
||||||
|
// docs: {
|
||||||
|
// routeBasePath: "/",
|
||||||
|
// sidebarPath: "./sidebars.ts",
|
||||||
|
// // Please change this to your repo.
|
||||||
|
// // Remove this to remove the "edit this page" links.
|
||||||
|
// editUrl: "https://github.com/codfrm/blog",
|
||||||
|
// showLastUpdateTime: true,
|
||||||
|
// },
|
||||||
|
} satisfies Preset.Options,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
```
|
||||||
|
|
||||||
|
引用时,在plugin单独引用:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
plugins: [
|
||||||
|
[
|
||||||
|
"docusaurus-plugin-content-docs-ex",
|
||||||
|
{
|
||||||
|
routeBasePath: "/",
|
||||||
|
sidebarPath: "./sidebars.ts",
|
||||||
|
// Please change this to your repo.
|
||||||
|
// Remove this to remove the "edit this page" links.
|
||||||
|
editUrl: "https://github.com/CodFrm/blog/edit/main",
|
||||||
|
showLastUpdateTime: true,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
然后就可以在插件中使用原有的 docs 插件,然后在原有的基础上添加一些新的功能。
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default async function pluginContentDocs(
|
||||||
|
context: LoadContext,
|
||||||
|
options: PluginOptions & { debug?: boolean }
|
||||||
|
): Promise<Plugin<LoadedContent>> {
|
||||||
|
const ret = (await docsPlugin.call(
|
||||||
|
this,
|
||||||
|
context,
|
||||||
|
options
|
||||||
|
)) as Plugin<LoadedContent>;
|
||||||
|
const themePath = path.resolve(__dirname, "./theme");
|
||||||
|
ret.getThemePath = () => {
|
||||||
|
return themePath;
|
||||||
|
};
|
||||||
|
|
||||||
|
const warpLoadContent = ret.loadContent;
|
||||||
|
ret.loadContent = async () => {
|
||||||
|
const ret = await warpLoadContent();
|
||||||
|
// ....一些操作
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
更多详细的内容你可以看我的插件:[docusaurus-plugin-content-docs-ex](https://github.com/CodFrm/blog/tree/main/packages/docusaurus-plugin-content-docs-ex)
|
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
@ -3,12 +3,41 @@ package code
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"io"
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type WrapReader struct {
|
||||||
|
len int
|
||||||
|
r io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WrapReader) Read(p []byte) (int, error) {
|
||||||
|
n, err := w.r.Read(p)
|
||||||
|
w.len += n
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReader(t *testing.T) {
|
||||||
|
file := bytes.NewBuffer([]byte("hello world"))
|
||||||
|
wr := WrapReader{len: 0, r: file}
|
||||||
|
_, _ = io.Copy(io.Discard, &wr)
|
||||||
|
t.Logf("文件长度: %d\n", wr.len)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGzip(t *testing.T) {
|
||||||
|
file := bytes.NewBuffer([]byte("hello world"))
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
w := gzip.NewWriter(buf)
|
||||||
|
_, _ = io.Copy(w, file)
|
||||||
|
t.Logf("不Close文件长度: %d\n", buf.Len())
|
||||||
|
w.Close()
|
||||||
|
t.Logf("压缩后文件长度: %d\n", buf.Len())
|
||||||
|
}
|
||||||
|
|
||||||
func TestIOHash(t *testing.T) {
|
func TestIOHash(t *testing.T) {
|
||||||
file := bytes.NewBuffer([]byte("hello world"))
|
file := bytes.NewBuffer([]byte("hello world"))
|
||||||
md5 := md5.New()
|
md5 := md5.New()
|
||||||
|
@ -1,31 +1,71 @@
|
|||||||
# Golang IO相关操作
|
# Golang IO 相关操作
|
||||||
|
|
||||||
- io包提供了基础的io操作,几乎所有的io操作都是基于io.Reader和io.Writer接口的。
|
- io 包提供了基础的 io 操作,几乎所有的 io 操作都是基于 io.Reader 和 io.Writer 接口的。
|
||||||
- ioutil 包提供了一些方便的io操作函数,但是在go 1.16中已经被废弃,放进了io包。
|
- ~~ioutil 包提供了一些方便的 io 操作函数,但是在 go 1.16 中已经被废弃,放进了 io 包。~~
|
||||||
- bufio实现了缓冲io,可以提高io效率。
|
- bufio 实现了缓冲 io,可以提高 io 效率。
|
||||||
- bytes 包中提供了Buffer类型,可以用来做io操作。
|
- bytes 包中提供了 Buffer 类型,可以用来做 io 操作。
|
||||||
|
|
||||||
## 核心接口
|
## 核心接口
|
||||||
|
|
||||||
### Reader/Writer/Seeker
|
Go 中几乎所有的 io 操作都是围绕着 Reader/Writer/Seeker 这三个接口进行,这三个接口都是独立开来的,可以随机组合。
|
||||||
|
|
||||||
Reader和Writer是io包中最重要的接口,它们定义了io操作的基本行为。像一些文件操作,网络操作等等,都是基于这两个接口的。
|
Reader 和 Writer 是 io 包中最重要的接口,它们定义了 io 操作的基本行为。像一些文件操作,网络操作等等,都是基于这两个接口的。
|
||||||
|
|
||||||
Seeker接口定义了Seek方法,可以在Reader/Writer中定位到指定的位置,文件操作中常用。
|
Seeker 接口定义了 Seek 方法,可以在 Reader/Writer 中定位到指定的位置,文件操作中常用。
|
||||||
|
|
||||||
|
还有一个 Close 接口,用来关闭 io,也是经常使用,有这个接口的时候,我们可以使用 defer 来关闭,避免忘记,而且一般情况下都需要注意关闭,否则会产生内存泄漏之类的问题。
|
||||||
|
|
||||||
|
有很多操作也是需要执行完 Close 之后才能生效,例如 gzip 压缩,如果不 Close,可能会导致压缩文件不完整。
|
||||||
|
|
||||||
|
```go
|
||||||
|
func TestGzip(t *testing.T) {
|
||||||
|
file := bytes.NewBuffer([]byte("hello world"))
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
w := gzip.NewWriter(buf)
|
||||||
|
_, _ = io.Copy(w, file)
|
||||||
|
t.Logf("不Close文件长度: %d\n", buf.Len())
|
||||||
|
w.Close()
|
||||||
|
t.Logf("压缩后文件长度: %d\n", buf.Len())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 基础使用
|
||||||
|
|
||||||
|
别看这三个接口简单,他们可以玩出很多花出来。例如对 Reader/Writer 进行包装,实现读取写入大小统计,来做流量统计时这很有用;标准库中也有很多方法,值得可以学习。
|
||||||
|
|
||||||
|
```go
|
||||||
|
type WrapReader struct {
|
||||||
|
len int
|
||||||
|
r io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WrapReader) Read(p []byte) (int, error) {
|
||||||
|
n, err := w.r.Read(p)
|
||||||
|
w.len += n
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReader(t *testing.T) {
|
||||||
|
file := bytes.NewBuffer([]byte("hello world"))
|
||||||
|
wr := WrapReader{len: 0, r: file}
|
||||||
|
_, _ = io.Copy(io.Discard, &wr)
|
||||||
|
t.Logf("文件长度: %d\n", wr.len)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## 常用函数
|
## 常用函数
|
||||||
|
|
||||||
下面我们来看一些常用的io操作函数,更多的函数可以查看官方文档,我只是列出一些我常用到的。
|
下面我们来看一些常用的 io 操作函数,更多的函数可以查看官方文档,我只是列出一些我常用到的。
|
||||||
|
|
||||||
- io.ReadeAll 读取所有数据,返回一个bytes
|
- io.ReadAll 读取所有数据,返回一个 bytes
|
||||||
- io.MultiWriter/Reader 可以将多个Reader/Writer合并成一个
|
- io.MultiWriter/Reader 可以将多个 Reader/Writer 合并成一个
|
||||||
- io.LimitReader 限制Reader的读取长度,读取n个长度后返回io.EOF
|
- io.LimitReader 限制 Reader 的读取长度,读取 n 个长度后返回 io.EOF
|
||||||
- io.TeeReader 读取数据的同时写入到另一个Writer中
|
- io.TeeReader 读取数据的同时写入到另一个 Writer 中
|
||||||
- io.Pipe 创建同步内存管道
|
- io.Pipe 创建同步内存管道
|
||||||
- io.Copy/CopyN 复制Reader到Writer
|
- io.Copy/CopyN 复制 Reader 到 Writer
|
||||||
- io.NopCloser 给一个io.Reader加上Close方法
|
- io.NopCloser 给一个 io.Reader 加上 Close 方法
|
||||||
- bytes.NewBuffer 创建一个Buffer
|
- bytes.NewBuffer 创建一个 Buffer
|
||||||
- bufio.NewReader/Writer 创建一个缓冲Reader/Writer
|
- bufio.NewReader/Writer 创建一个缓冲 Reader/Writer
|
||||||
|
|
||||||
## 常见小坑
|
## 常见小坑
|
||||||
|
|
||||||
@ -33,32 +73,32 @@ Seeker接口定义了Seek方法,可以在Reader/Writer中定位到指定的位
|
|||||||
|
|
||||||
### io.EOF
|
### io.EOF
|
||||||
|
|
||||||
io.EOF是io包中定义的一个错误,表示读取到文件末尾。在读取文件时,当读取到文件末尾时,会返回io.EOF错误。
|
io.EOF 是 io 包中定义的一个错误,表示读取到文件末尾。在读取文件时,当读取到文件末尾时,会返回 io.EOF 错误。
|
||||||
|
|
||||||
请注意,当读取到文件末尾时,返回的数据可能不是nil,而是文件的最后一部分数据,需要注意处理。
|
请注意,当读取到文件末尾时,返回的数据可能不是 nil,而是文件的最后一部分数据,需要注意处理。
|
||||||
|
|
||||||
### 大文件的操作
|
### 大文件的操作
|
||||||
|
|
||||||
在处理大文件时,需要注意内存的使用,尽量使用io.Reader和io.Writer来处理文件,避免一次性读取整个文件到内存中。
|
在处理大文件时,需要注意内存的使用,尽量使用 io.Reader 和 io.Writer 来处理文件,避免一次性读取整个文件到内存中。
|
||||||
|
|
||||||
### bufio.Writer.Flush
|
### bufio.Writer.Flush
|
||||||
|
|
||||||
在使用bufio.Writer时,需要注意Flush方法,因为bufio.Writer是带缓冲的,数据并不是实时写入到Writer中的,需要调用Flush方法来刷新缓冲区。
|
在使用 bufio.Writer 时,需要注意 Flush 方法,因为 bufio.Writer 是带缓冲的,数据并不是实时写入到 Writer 中的,需要调用 Flush 方法来刷新缓冲区。
|
||||||
|
|
||||||
### io.Pipe
|
### io.Pipe
|
||||||
|
|
||||||
io.Pipe是一个同步的内存管道,请注意在使用时,Reader与Writer必须在不同的goroutine中,且需要注意Reader与Writer的速度需要一致,否则会造成死锁或线程阻塞。
|
io.Pipe 是一个同步的内存管道,请注意在使用时,Reader 与 Writer 必须在不同的 goroutine 中,且需要注意 Reader 与 Writer 的速度需要一致,否则会造成死锁或线程阻塞。
|
||||||
|
|
||||||
并且需要使用Close方法来关闭Reader或Writer,否则会造成内存泄漏。
|
并且需要使用 Close 方法来关闭 Reader 或 Writer,否则会造成内存泄漏。
|
||||||
|
|
||||||
## 高级用例
|
## 高级用例
|
||||||
|
|
||||||
下面介绍一些高级用例,你也可以从中学习到一些高级的io操作技巧。
|
下面介绍一些高级用例,你也可以从中学习到一些高级的 io 操作技巧。
|
||||||
|
|
||||||
### 计算上传文件的hash
|
### 计算上传文件的 hash
|
||||||
|
|
||||||
下面的例子是计算上传文件的md5和sha1的hash值,我们使用io.TeeReader将文件内容同时写入到md5和sha1的hash计算器中。
|
下面的例子是计算上传文件的 md5 和 sha1 的 hash 值,我们使用 io.TeeReader 将文件内容同时写入到 md5 和 sha1 的 hash 计算器中。
|
||||||
像我们在http上传文件时,可以在上传的同时计算文件的hash值,这样可以保证文件的完整性。同时也可以打开文件,再将文件的Writer放到io.Copy中,这样可以同时写入文件和计算hash值。
|
像我们在 http 上传文件时,可以在上传的同时计算文件的 hash 值,这样可以保证文件的完整性。同时也可以打开文件,再将文件的 Writer 放到 io.Copy 中,这样可以同时写入文件和计算 hash 值。
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func TestIOHash(t *testing.T) {
|
func TestIOHash(t *testing.T) {
|
||||||
@ -74,7 +114,7 @@ func TestIOHash(t *testing.T) {
|
|||||||
|
|
||||||
### 读取文件的部分内容
|
### 读取文件的部分内容
|
||||||
|
|
||||||
下面的例子是读取文件的部分内容,我们使用io.LimitReader限制读取的长度,读取n个长度后返回io.EOF,这在解析文件头部信息时非常有用。
|
下面的例子是读取文件的部分内容,我们使用 io.LimitReader 限制读取的长度,读取 n 个长度后返回 io.EOF,这在解析文件头部信息时非常有用。
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func TestLimitReader(t *testing.T) {
|
func TestLimitReader(t *testing.T) {
|
||||||
@ -87,9 +127,9 @@ func TestLimitReader(t *testing.T) {
|
|||||||
|
|
||||||
### 大文件按行读取
|
### 大文件按行读取
|
||||||
|
|
||||||
下面的例子是读取大文件的每一行,我们使用bufio.Reader来读取文件的每一行,这样可以避免一次性读取整个文件到内存中。
|
下面的例子是读取大文件的每一行,我们使用 bufio.Reader 来读取文件的每一行,这样可以避免一次性读取整个文件到内存中。
|
||||||
|
|
||||||
在读一些io比较慢的文件时(网络文件),也可以使用这种方式来读取文件。写文件也是类似的,可以使用bufio.Writer来写文件,提高磁盘io效率,避免频繁的io操作。
|
在读一些 io 比较慢的文件时(网络文件),也可以使用这种方式来读取文件。写文件也是类似的,可以使用 bufio.Writer 来写文件,提高磁盘 io 效率,避免频繁的 io 操作。
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func TestBufIo(t *testing.T) {
|
func TestBufIo(t *testing.T) {
|
||||||
@ -106,9 +146,9 @@ func TestBufIo(t *testing.T) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 使用Pipe将上传文件解密写入文件
|
### 使用 Pipe 将上传文件解密写入文件
|
||||||
|
|
||||||
有时候上传的文件是一个加密的,我们需要在上传的同时解密文件,然后同时写入到磁盘中,这时候可以使用io.Pipe来实现。
|
有时候上传的文件是一个加密的,我们需要在上传的同时解密文件,然后同时写入到磁盘中,这时候可以使用 io.Pipe 来实现。
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
|
||||||
|
@ -172,7 +172,7 @@ func ModifySlice(slice []int) {
|
|||||||
|
|
||||||
在1.18之前,切片的扩容是原来的2倍,但是当容量超过1024时,每次容量变成原来的1.25倍,直到大于期望容量。在1.18后更换了新的机制:
|
在1.18之前,切片的扩容是原来的2倍,但是当容量超过1024时,每次容量变成原来的1.25倍,直到大于期望容量。在1.18后更换了新的机制:
|
||||||
|
|
||||||
<https://github.com/golang/go/blob/27f41bb15391668fa8ba18561efe364bab9b8312/src/runtime/slice.go#L267>
|
[src/runtime/slice.go#L267](https://github.com/golang/go/blob/27f41bb15391668fa8ba18561efe364bab9b8312/src/runtime/slice.go#L267)
|
||||||
|
|
||||||
- 当新切片>旧切片\*2时,直接安装新切片容量计算
|
- 当新切片>旧切片\*2时,直接安装新切片容量计算
|
||||||
- 如果旧切片\<256,新切片容量为旧切片\*2
|
- 如果旧切片\<256,新切片容量为旧切片\*2
|
||||||
@ -195,7 +195,7 @@ func TestCap(t *testing.T) {
|
|||||||
|
|
||||||
至于实际的结果为什么没有和上述说的一样,可以看到,在`nextslicecap`计算出容量后续,还有对`newcap`的一系列操作,这是内存对齐的一系列计算。
|
至于实际的结果为什么没有和上述说的一样,可以看到,在`nextslicecap`计算出容量后续,还有对`newcap`的一系列操作,这是内存对齐的一系列计算。
|
||||||
|
|
||||||
<https://github.com/golang/go/blob/27f41bb15391668fa8ba18561efe364bab9b8312/src/runtime/slice.go#L188>
|
[src/runtime/slice.go#L188](https://github.com/golang/go/blob/27f41bb15391668fa8ba18561efe364bab9b8312/src/runtime/slice.go#L188)
|
||||||
|
|
||||||
逻辑比较复杂,可以进入调试模式跟踪逻辑,这里就不多展开了
|
逻辑比较复杂,可以进入调试模式跟踪逻辑,这里就不多展开了
|
||||||
|
|
||||||
|
@ -4,8 +4,8 @@ import type * as Preset from "@docusaurus/preset-classic";
|
|||||||
import docsToBlog from "./packages/docusaurus-plugin-docs-info/src";
|
import docsToBlog from "./packages/docusaurus-plugin-docs-info/src";
|
||||||
|
|
||||||
const config: Config = {
|
const config: Config = {
|
||||||
title: "王一之",
|
title: "一知",
|
||||||
tagline: "王一之的个人博客:分享知识,记录生活,认识朋友",
|
tagline: "王一之的个人博客:分享知识,认识朋友",
|
||||||
favicon: "img/favicon.ico",
|
favicon: "img/favicon.ico",
|
||||||
|
|
||||||
// Set the production url of your site here
|
// Set the production url of your site here
|
||||||
@ -77,7 +77,7 @@ const config: Config = {
|
|||||||
// Replace with your project's social card
|
// Replace with your project's social card
|
||||||
image: "img/docusaurus-social-card.jpg",
|
image: "img/docusaurus-social-card.jpg",
|
||||||
navbar: {
|
navbar: {
|
||||||
title: "王一之的博客",
|
title: "一知",
|
||||||
logo: {
|
logo: {
|
||||||
alt: "Logo",
|
alt: "Logo",
|
||||||
src: "img/avatar.png",
|
src: "img/avatar.png",
|
||||||
|
@ -36,11 +36,6 @@ export default async function pluginContentDocs(
|
|||||||
)) as Plugin<LoadedContent>;
|
)) as Plugin<LoadedContent>;
|
||||||
const isProd = process.env.NODE_ENV === "production";
|
const isProd = process.env.NODE_ENV === "production";
|
||||||
|
|
||||||
const themePath = path.resolve(__dirname, "./theme");
|
|
||||||
ret.getThemePath = () => {
|
|
||||||
return themePath;
|
|
||||||
};
|
|
||||||
|
|
||||||
const warpLoadContent = ret.loadContent;
|
const warpLoadContent = ret.loadContent;
|
||||||
ret.loadContent = async () => {
|
ret.loadContent = async () => {
|
||||||
const ret = await warpLoadContent();
|
const ret = await warpLoadContent();
|
||||||
|
@ -9,12 +9,11 @@ export default function HeadingWrapper(props) {
|
|||||||
const syntheticTitle = useSyntheticTitle();
|
const syntheticTitle = useSyntheticTitle();
|
||||||
const doc = useDoc();
|
const doc = useDoc();
|
||||||
const detail = (doc.metadata as any).detail as Detail;
|
const detail = (doc.metadata as any).detail as Detail;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Heading {...props}>
|
<Heading {...props}>
|
||||||
{props.children}
|
{props.children}
|
||||||
{detail && !syntheticTitle && (
|
{detail && !syntheticTitle && props.as.toString() === "h1" && doc.contentTitle==props.children && (
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
display: "block",
|
display: "block",
|
||||||
|
@ -6,9 +6,10 @@ export default function AntdProvider({ children }) {
|
|||||||
return (
|
return (
|
||||||
<ConfigProvider
|
<ConfigProvider
|
||||||
theme={{
|
theme={{
|
||||||
algorithm: colorMode.isDarkTheme
|
algorithm:
|
||||||
? theme.darkAlgorithm
|
colorMode.colorMode == "dark"
|
||||||
: theme.defaultAlgorithm,
|
? theme.darkAlgorithm
|
||||||
|
: theme.defaultAlgorithm,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user