Skip to content

Commit

Permalink
[gem-book] Support local search
Browse files Browse the repository at this point in the history
Closed #114
  • Loading branch information
mantou132 committed Jan 14, 2024
1 parent 6096128 commit 615ebf6
Show file tree
Hide file tree
Showing 25 changed files with 696 additions and 291 deletions.
12 changes: 12 additions & 0 deletions packages/duoyun-ui/docs/template.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
</head>
<body>
<gem-book>
<gbp-docsearch slot="nav-inside"></gbp-docsearch>
</gem-book>
</body>
</html>
4 changes: 3 additions & 1 deletion packages/duoyun-ui/gem-book.cli.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
{
"$schema": "../gem-book/schema.json",
"template": "docs/template.html",
"title": "DuoyunUI",
"icon": "../../logo.png",
"i18n": true,
"sourceBranch": "docs",
"plugin": ["media", "api", "sandpack", "raw"],
"plugin": ["media", "api", "sandpack", "raw", "docsearch?local"],
"debug": true
}
8 changes: 4 additions & 4 deletions packages/gem-book/docs/zh/003-plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,15 @@ yarn add gem-book

## `<gbp-docsearch>`

使用 [DocSearch](https://docsearch.algolia.com/) 为网站提供搜索,只需要实例化一次,可以使用[插槽](./guide/007-extension.md#插槽)放在想要放置的位置:
使用 [Algolia DocSearch](https://docsearch.algolia.com/) 为网站提供搜索,只需要实例化一次,可以使用[插槽](./guide/007-extension.md#插槽)放在想要放置的位置:

<gbp-raw src="docs/template.html" range="13--4"></gbp-raw>

> [!WARNING]
>
> - DocSearch Crawler [配置](https://crawler.algolia.com/admin/crawlers)中必须启用 `renderJavaScript`
> - 全文搜索需要等待 DocSearch [支持](https://github.com/algolia/renderscript/pull/555) ShadowDOM,
> 可以使用 [`--site`](./002-cli.md#--site-url) 暂时为网站添加标题搜索(DocSearch Crawler 配置修改 `sitemaps` 字段)
> Algolia DocSearch Crawler [配置](https://crawler.algolia.com/admin/crawlers)中必须启用 `renderJavaScript`
使用 `docsearch?local` 可以提供本地搜索服务(感谢 [MiniSearch](https://github.com/lucaong/minisearch/)),[例子](https://duoyun-ui.gemjs.org)

## `<gbp-comment>`

Expand Down
5 changes: 3 additions & 2 deletions packages/gem-book/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@
],
"scripts": {
"schema": "npx ts-json-schema-generator -p src/common/config.ts -t CliConfig -o schema.json",
"build:cli": "esbuild ./src/bin/index.ts --outdir=./bin --platform=node --sourcemap --bundle --external:anymatch --external:cheerio --external:chokidar --external:jimp --external:marked --external:yaml --external:front-matter --external:commander --external:webpack --external:ts-loader --external:typescript --external:webpack-dev-server --external:html-webpack-plugin --external:copy-webpack-plugin --external:workbox-webpack-plugin",
"build:cli": "esbuild ./src/bin/index.ts --tsconfig=./tsconfig.cli.json --outdir=./bin --platform=node --sourcemap --bundle --external:anymatch --external:cheerio --external:chokidar --external:jimp --external:marked --external:yaml --external:front-matter --external:commander --external:webpack --external:ts-loader --external:typescript --external:webpack-dev-server --external:html-webpack-plugin --external:copy-webpack-plugin --external:workbox-webpack-plugin",
"start:cli": "yarn build:cli --watch",
"docs": "node ./bin docs",
"start:docs": "cross-env PORT=8090 GEM_BOOK_DEV=true nodemon --watch bin --exec \"yarn docs\"",
"start": "concurrently npm:start:cli npm:start:docs",
"build:website": "yarn build:cli && yarn docs --build --ga G-7X2Z4B2KV0",
"build": "yarn build:cli && tsc -p ./tsconfig.build.json",
"build": "yarn build:cli && tsc",
"test": "cross-env NODE_OPTIONS=--no-experimental-fetch wtr",
"prepublishOnly": "yarn build"
},
Expand All @@ -45,6 +45,7 @@
"chokidar": "^3.5.3",
"commander": "^7.2.0",
"copy-webpack-plugin": "^11.0.0",
"express": "^4.17.3",
"front-matter": "^4.0.2",
"git-remote-origin-url": "^3.1.0",
"git-repo-info": "^2.1.1",
Expand Down
121 changes: 96 additions & 25 deletions packages/gem-book/src/bin/builder.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import path from 'path';
import { writeFileSync, symlinkSync, renameSync } from 'fs';

import webpack from 'webpack';
import webpack, { DefinePlugin, Compilation, Compiler, sources } from 'webpack';
import serveStatic from 'serve-static';
import WebpackDevServer from 'webpack-dev-server';
import HtmlWebpackPlugin from 'html-webpack-plugin';
Expand All @@ -11,14 +11,52 @@ import { GenerateSW } from 'workbox-webpack-plugin';

import { BookConfig, CliUniqueConfig, NavItem } from '../common/config';
import { STATS_FILE } from '../common/constant';
import { getLinkPath } from '../common/utils';
import { getBody, getLinkPath, getUserLink } from '../common/utils';

import { resolveLocalPlugin, resolveTheme, isURL, importObject, print } from './utils';
import { resolveLocalPlugin, resolveTheme, isURL, importObject, print, getMdFile, getMetadata } from './utils';

const publicDir = path.resolve(__dirname, '../public');
const entryDir = path.resolve(__dirname, process.env.GEM_BOOK_DEV ? '../src/website' : '../website');
const pluginDir = path.resolve(__dirname, process.env.GEM_BOOK_DEV ? '../src/plugins' : '../plugins');

export interface MdDocument {
id: string;
title: string;
text: string;
}

function genDocuments(docsDir: string, bookConfig: BookConfig) {
const gen = (sidebar: NavItem[], lang = '') => {
// 防止文件夹重复
const addedLinks = new Set<string>();
const documents: MdDocument[] = [];
const temp = [...sidebar];
while (temp.length) {
const item = temp.pop()!;
if (addedLinks.has(item.link)) continue;
if (item.sidebarIgnore) continue;
if (item.children) temp.push(...item.children);
if (item.type === 'file' || item.type === 'dir') {
addedLinks.add(item.link);
const fullPath = path.join(docsDir, lang, item.link);
documents.push({
id: getLinkPath(item.link, bookConfig.displayRank),
text: item.type === 'file' ? getBody(getMdFile(fullPath).content) : '',
title: getMetadata(fullPath, bookConfig.displayRank).title,
});
}
}
return documents;
};
if (Array.isArray(bookConfig.sidebar)) {
return { '': gen(bookConfig.sidebar) };
} else {
return Object.fromEntries(
Object.entries(bookConfig.sidebar || {}).map(([lang, { data }]) => [lang, gen(data, lang)]),
);
}
}

function genPaths(bookConfig: BookConfig) {
const result: string[] = [];
const gen = (sidebar: NavItem[], lang = '') => {
Expand All @@ -28,7 +66,7 @@ function genPaths(bookConfig: BookConfig) {
if (item.sidebarIgnore) continue;
if (item.children) temp.push(...item.children);
if (item.type === 'file') {
result.push(`${lang ? `/${lang}` : ''}${getLinkPath(item.link, bookConfig.displayRank)}`);
result.push(`${lang ? `/${lang}` : ''}${getUserLink(item.link, bookConfig.displayRank)}`);
}
}
};
Expand All @@ -42,30 +80,41 @@ function genPaths(bookConfig: BookConfig) {
return result;
}

function getPluginRecord(options: Required<CliUniqueConfig>) {
return Object.fromEntries<{ name: string; url: string }>(
options.plugin.map((plugin) => {
const [base, ...rest] = plugin.split(/(\?)/);
const search = rest.join('');
const pluginPath = resolveLocalPlugin(base);
if (!pluginPath) return [plugin, { name: plugin, url: 'file:' + plugin }];
if (pluginPath.custom) {
const filename = path.basename(pluginPath.custom);
const uniqueFilename = filename + Date.now();
const symLinkPath = path.resolve(pluginDir, filename);
symlinkSync(pluginPath.custom, path.resolve(pluginDir, uniqueFilename));
// 替换内置文件
renameSync(path.resolve(pluginDir, uniqueFilename), symLinkPath);
return [pluginPath.custom, { name: filename, url: 'file:' + filename + search }];
}
return [pluginPath.builtIn!, { name: base, url: 'file:' + base + search }];
}),
);
}

// dev mode uses memory file system
export async function build(dir: string, options: Required<CliUniqueConfig>, bookConfig: BookConfig) {
const { debug, build, theme, template, output, icon, plugin, ga } = options;

const plugins = [...plugin];

plugins.forEach((plugin, index) => {
const localPath = resolveLocalPlugin(plugin);
if (localPath) {
const filename = path.basename(localPath);
const uniqueFilename = filename + Date.now();
symlinkSync(localPath, path.resolve(pluginDir, uniqueFilename));
renameSync(path.resolve(pluginDir, uniqueFilename), path.resolve(pluginDir, filename));
// load from `plugins` dir
plugins[index] = filename;
}
});

const { debug, build, theme, template, output, icon, ga } = options;
const isRemoteIcon = isURL(icon);
const docsDir = path.resolve(dir);
// 开发模式时使用 docsDir 避免不必要的复制
const outputDir = build && output ? path.resolve(output) : docsDir;
const pluginRecord = getPluginRecord(options);
const plugins = Object.values(pluginRecord);
const themePath = resolveTheme(theme);

const docSearchPlugin = plugins.find((e) => e.name === 'docsearch')?.url;
const isLocalSearch = docSearchPlugin && new URL(docSearchPlugin).searchParams.has('local');

const compiler = webpack({
stats: build ? 'normal' : 'errors-warnings',
mode: build ? 'production' : 'development',
Expand Down Expand Up @@ -114,16 +163,38 @@ export async function build(dir: string, options: Required<CliUniqueConfig>, boo
viewport: 'width=device-width, initial-scale=1, shrink-to-fit=no',
},
}),
new webpack.DefinePlugin({
new DefinePlugin({
'import.meta.url': DefinePlugin.runtimeValue(({ module }) =>
// 如果是符号链接返回的是原始路径
JSON.stringify(pluginRecord[module.resource]?.url),
),
'process.env.DEV_MODE': !build,
'process.env.BOOK_CONFIG': JSON.stringify(bookConfig),
'process.env.THEME': JSON.stringify(await importObject(themePath)),
'process.env.PLUGINS': JSON.stringify(plugins),
'process.env.PLUGINS': JSON.stringify(plugins.map(({ name }) => name)),
'process.env.GA_ID': JSON.stringify(ga),
}),
new CopyWebpackPlugin({
patterns: [{ from: publicDir, to: outputDir }],
}),
new CopyWebpackPlugin({ patterns: [{ from: publicDir, to: outputDir }] }),
isLocalSearch && {
apply(compiler: Compiler) {
compiler.hooks.compilation.tap('json-webpack-plugin', (compilation) => {
compilation.hooks.processAssets.tapPromise(
{
name: 'json-webpack-plugin',
stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,
},
async () => {
Object.entries(genDocuments(docsDir, bookConfig)).forEach(([lang, documents]) => {
compilation.emitAsset(
['documents', lang, 'json'].filter((e) => !!e).join('.'),
new sources.RawSource(JSON.stringify(documents)),
);
});
},
);
});
},
},
options.site && new SitemapPlugin({ base: options.site, paths: genPaths(bookConfig) }),
build &&
new GenerateSW({
Expand Down
Loading

0 comments on commit 615ebf6

Please sign in to comment.