Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 40 additions & 55 deletions website/docusaurus.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type * as Preset from '@docusaurus/preset-classic';
import type * as PresetMermaid from '@docusaurus/theme-mermaid';
import type { Config } from '@docusaurus/types';
import { GlobExcludeDefault } from '@docusaurus/utils';
import { GlobExcludeDefault, getVcsPreset } from '@docusaurus/utils';
import { createRequire } from 'module';
import path from 'path';
import { themes } from 'prism-react-renderer';
Expand All @@ -18,12 +18,30 @@ require('./scripts/generateMonacoPreloads.cjs').ensureMonacoPreloadManifest();
const isDev = process.env.NODE_ENV === 'development';

// docs/core is shared by React (/docs) and Vue (/vue); see framework-docs/
const {
docsInstance,
frameworkInstance,
} = require('./framework-docs/docsInstances.js');
const frameworkDocs = require('./framework-docs/index.js');
const remarkFramework = require('./framework-docs/remarkFramework.js');
// Non-Vue instances render React; :::vue reaches Vue agents via skill references
const reactRemarkPlugins = [[remarkFramework, { framework: 'react' }]];
const vueDocs = frameworkDocs.generate('vue');
if (isDev) frameworkDocs.watch('vue');
const vueInstance = frameworkInstance('vue');
const gitVcs = getVcsPreset('default-v1');
const editRoot = 'https://github.com/reactive/data-client/edit/master';
/** Plugin options locating a docs instance (framework-docs/docsInstances.js) */
const docsLocation = (id: string) => {
const { path: docsPath, routeBasePath } = docsInstance(id);
return {
id,
path: `../${docsPath}`,
routeBasePath,
editUrl: ({ docPath }: { docPath: string }) =>
`${editRoot}/${docsPath}/${docPath}`,
};
};

const config: Config = {
title: 'Data Client',
Expand Down Expand Up @@ -208,33 +226,32 @@ const config: Config = {
repoUrl: 'https://github.com/reactive/data-client',
},
onBrokenLinks: 'log',
future: {
// Generated Vue mirror pages have no git history; read their source's
experimental_vcs: {
...gitVcs,
getFileCreationInfo: file =>
gitVcs.getFileCreationInfo(vueDocs.sourceOf(file)),
getFileLastUpdateInfo: file =>
gitVcs.getFileLastUpdateInfo(vueDocs.sourceOf(file)),
},
},
presets: [
[
'@docusaurus/preset-classic',
{
docs: {
//id: 'core',
path: '../docs/core',
...docsLocation('default'),
// `exclude` replaces Docusaurus' defaults; keep them so `_` partials aren't published
exclude: [
...GlobExcludeDefault,
'getting-started/README.md',
'**/*.vue.{md,mdx}',
],
//routeBasePath: 'core',
sidebarPath: require.resolve('./framework-docs/sidebars-react.js'),
beforeDefaultRemarkPlugins: reactRemarkPlugins,
showLastUpdateAuthor: true,
showLastUpdateTime: true,
editUrl: ({ locale, docPath }) => {
/*if (locale !== 'en') {
return `https://crowdin.com/project/docusaurus-v2/${locale}`;
}*/
// We want users to submit doc updates to the upstream/next version!
// Otherwise we risk losing the update on the next release.
const nextVersionDocsDirPath = 'docs';
return `https://github.com/reactive/data-client/edit/master/${nextVersionDocsDirPath}/${docPath}`;
},
lastVersion: 'current',
includeCurrentVersion: true,
versions: {
Expand Down Expand Up @@ -271,47 +288,34 @@ const config: Config = {
[
'@docusaurus/plugin-content-docs',
{
id: 'vue',
...docsLocation('vue'),
path: vueDocs.outDir,
exclude: [...GlobExcludeDefault, 'getting-started/README.md'],
routeBasePath: 'vue',
sidebarPath: require.resolve('./framework-docs/sidebars-vue.js'),
beforeDefaultRemarkPlugins: [
[
remarkFramework,
{
framework: 'vue',
routeBasePath: 'vue',
routeBasePath: vueInstance.routeBasePath,
docIds: frameworkDocs.docIds('vue'),
},
],
],
// generated files have no git history
showLastUpdateAuthor: false,
showLastUpdateTime: false,
showLastUpdateAuthor: true,
showLastUpdateTime: true,
editUrl: ({ docPath }) =>
`https://github.com/reactive/data-client/edit/master/docs/core/${frameworkDocs.sourcePath('vue', docPath)}`,
`${editRoot}/${vueInstance.path}/${frameworkDocs.sourcePath('vue', docPath)}`,
},
],
[
'@docusaurus/plugin-content-docs',
{
id: 'rest',
path: '../docs/rest',
routeBasePath: 'rest',
...docsLocation('rest'),
sidebarPath: require.resolve('./sidebars-rest.js'),
beforeDefaultRemarkPlugins: reactRemarkPlugins,
showLastUpdateAuthor: true,
showLastUpdateTime: true,
editUrl: ({ locale, docPath }) => {
/*if (locale !== 'en') {
return `https://crowdin.com/project/docusaurus-v2/${locale}`;
}*/
// We want users to submit doc updates to the upstream/next version!
// Otherwise we risk losing the update on the next release.
const nextVersionDocsDirPath = 'docs/rest';
return `https://github.com/reactive/data-client/edit/master/${nextVersionDocsDirPath}/${docPath}`;
},
lastVersion: 'current',
includeCurrentVersion: true,
versions: {
Expand All @@ -325,22 +329,11 @@ const config: Config = {
[
'@docusaurus/plugin-content-docs',
{
id: 'graphql',
path: '../docs/graphql',
routeBasePath: 'graphql',
...docsLocation('graphql'),
sidebarPath: require.resolve('./sidebars-graphql.js'),
beforeDefaultRemarkPlugins: reactRemarkPlugins,
showLastUpdateAuthor: true,
showLastUpdateTime: true,
editUrl: ({ locale, docPath }) => {
/*if (locale !== 'en') {
return `https://crowdin.com/project/docusaurus-v2/${locale}`;
}*/
// We want users to submit doc updates to the upstream/next version!
// Otherwise we risk losing the update on the next release.
const nextVersionDocsDirPath = 'docs/graphql';
return `https://github.com/reactive/data-client/edit/master/${nextVersionDocsDirPath}/${docPath}`;
},
lastVersion: 'current',
includeCurrentVersion: true,
versions: {
Expand All @@ -356,7 +349,8 @@ const config: Config = {
{
// Vue docs briefly lived at /docs/vue
createRedirects(existingPath: string) {
if (existingPath === '/vue' || existingPath.startsWith('/vue/'))
const vue = `/${vueInstance.routeBasePath}`;
if (existingPath === vue || existingPath.startsWith(`${vue}/`))
return `/docs${existingPath}`;
},
redirects: [
Expand Down Expand Up @@ -424,16 +418,7 @@ const config: Config = {
path.resolve(__dirname, './node-plugin'),
path.resolve(__dirname, './profiling-plugin'),
path.resolve(__dirname, './raw-plugin'),
[
path.resolve(__dirname, './llms-plugin'),
{
frameworks: {
react: { id: 'default', path: '/', name: 'React' },
vue: { id: 'vue', path: '/vue/', name: 'Vue' },
},
shared: { rest: 'REST', graphql: 'GraphQL' },
},
],
path.resolve(__dirname, './llms-plugin'),
],
themeConfig: {
mermaid: {
Expand Down
3 changes: 3 additions & 0 deletions website/framework-docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ no text left for a framework (e.g. only `:react[...]`) is dropped from that fram
- Docusaurus can't point two docs instances at one folder, so `index.js` mirrors `docs/core` into
`docs/.core-vue` (gitignored; a sibling so relative imports into `docs/rest` keep working), applying `.vue.md` overrides, `vue_` front matter and
`frameworks:` filtering. It runs on config load and re-syncs on change during `yarn start`.
- `docsInstances.js` lists every docs instance (id, source folder, route, `llms.txt` path).
`docusaurus.config.ts`, `docsToMarkdown.mjs`, `llms-plugin.js` and `remarkFramework.js`
(`FRAMEWORKS`) read routes and frameworks from it.
- `FrameworkSelector` (in the breadcrumbs) switches to the same page in the other docs instance,
and disables a framework when the page doesn't exist there.

Expand Down
55 changes: 55 additions & 0 deletions website/framework-docs/docsInstances.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/* global module */
/**
* Every docs instance on the site, read by docusaurus.config.ts (plugin
* options), docsToMarkdown.mjs (routes) and llms-plugin.js (llms.txt).
*
* Instances with a `framework` render docs/core for that framework (Vue via
* the generated mirror, see index.js); the rest are shared by every framework.
*
* - id: docs plugin instance id
* - path: source folder, relative to the repo root (for Vue, the source its
* mirror is generated from)
* - routeBasePath: site route of the instance
* - llms: where that framework's llms.txt and llms-full.txt are served
*/
const DOCS_INSTANCES = [
{
id: 'default',
framework: 'react',
name: 'React',
path: 'docs/core',
routeBasePath: 'docs',
llms: '/',
},
{
id: 'vue',
framework: 'vue',
name: 'Vue',
path: 'docs/core',
routeBasePath: 'vue',
llms: '/vue/',
},
{ id: 'rest', name: 'REST', path: 'docs/rest', routeBasePath: 'rest' },
{
id: 'graphql',
name: 'GraphQL',
path: 'docs/graphql',
routeBasePath: 'graphql',
},
];

/** Instances rendering docs/core, one per framework */
const FRAMEWORK_INSTANCES = DOCS_INSTANCES.filter(d => d.framework);
const FRAMEWORKS = FRAMEWORK_INSTANCES.map(d => d.framework);

const docsInstance = id => DOCS_INSTANCES.find(d => d.id === id);
const frameworkInstance = framework =>
FRAMEWORK_INSTANCES.find(d => d.framework === framework);

module.exports = {
DOCS_INSTANCES,
FRAMEWORK_INSTANCES,
FRAMEWORKS,
docsInstance,
frameworkInstance,
};
40 changes: 24 additions & 16 deletions website/framework-docs/docsToMarkdown.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const require = createRequire(import.meta.url);
const preprocessContent =
require('@docusaurus/mdx-loader/lib/preprocessor').default;

const { DOCS_INSTANCES, frameworkInstance } = require('./docsInstances.js');
const {
docIds,
docIdOf,
Expand All @@ -37,12 +38,13 @@ const remarkFramework = require('./remarkFramework.js');

export { ROOT, SITE, rel };

/** docs folder -> route base per framework; keep in sync with docusaurus.config.ts */
const ROUTES = [
['docs/core/', { react: '/docs/', vue: '/vue/' }],
['docs/rest/', { react: '/rest/', vue: '/rest/' }],
['docs/graphql/', { react: '/graphql/', vue: '/graphql/' }],
];
/** Docs instance rendering `relPath` (from the repo root) for a framework */
const instanceOf = (relPath, framework) =>
DOCS_INSTANCES.find(
d =>
relPath.startsWith(`${d.path}/`) &&
(d.framework ?? framework) === framework,
);
const vueIds = docIds('vue');
const MD = /\.mdx?$/;

Expand Down Expand Up @@ -107,16 +109,18 @@ const parse = memoize(file => {
/** Site route (no host) of a doc for a framework */
export const routeOf = memoize((file, framework) => {
const relPath = rel(file).replace(/\.(react|vue)(\.mdx?)$/, '$2');
const match = ROUTES.find(([dir]) => relPath.startsWith(dir));
if (!match) return;
const [dir, bases] = match;
const docId = docIdOf(relPath.slice(dir.length), contentFor(file, framework));
const instance = instanceOf(relPath, framework);
if (!instance) return;
const docId = docIdOf(
relPath.slice(instance.path.length + 1),
contentFor(file, framework),
);
// Vue links to pages without a Vue version go to the React docs
const base =
dir === 'docs/core/' && framework === 'vue' && !vueIds.has(docId) ?
bases.react
: bases[framework];
return `${base}${docId}`.replace(/\/index$/, '/');
const { routeBasePath } =
instance.framework === 'vue' && !vueIds.has(docId) ?
frameworkInstance('react')
: instance;
return `/${routeBasePath}/${docId}`.replace(/\/index$/, '/');
});

/** Relative doc links become site routes; absolute ones are left to remarkFramework */
Expand Down Expand Up @@ -482,7 +486,11 @@ function render(file, framework, props = {}) {
tree.children = convertAll(tree.children);
// absolute /docs links point at this framework's docs, as on the site
if (framework === 'vue')
remarkFramework({ framework, routeBasePath: 'vue', docIds: vueIds })(tree);
remarkFramework({
framework,
routeBasePath: frameworkInstance('vue').routeBasePath,
docIds: vueIds,
})(tree);
tree.title = frontMatterValue(content, 'title');
return tree;
}
Expand Down
13 changes: 10 additions & 3 deletions website/framework-docs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
const fs = require('fs');
const path = require('path');

const { FRAMEWORKS } = require('./remarkFramework.js');
const { FRAMEWORKS, frameworkInstance } = require('./docsInstances.js');

const SRC = path.resolve(__dirname, '../../docs/core');
const SRC = path.resolve(__dirname, '../..', frameworkInstance('react').path);
const MD = /\.mdx?$/;
/** `foo.vue.md` replaces `foo.md` for Vue */
const VUE_OVERRIDE = /\.vue(\.mdx?)$/;
Expand Down Expand Up @@ -115,7 +115,14 @@ function generate(framework) {
if (!sources.has(file)) fs.rmSync(path.join(outDir, file));
}
}
return { outDir, sources };
/** Source of a mirror file (itself if not mirrored), for its git history */
const sourceOf = file => {
const src = sources.get(
path.relative(outDir, file).split(path.sep).join('/'),
);
return src ? path.join(SRC, src) : file;
};
return { outDir, sources, sourceOf };
}

/** Keep the mirror in sync during `docusaurus start` */
Expand Down
5 changes: 3 additions & 2 deletions website/framework-docs/remarkFramework.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* global module */
/* global module, require */
/**
* Remark plugin that resolves framework-specific content in shared docs.
*
Expand All @@ -16,7 +16,8 @@
* instance when the target doc exists in it (`docIds`), so Vue pages link to
* Vue pages; links to React-only docs keep going to /docs.
*/
const FRAMEWORKS = ['react', 'vue'];
const { FRAMEWORKS } = require('./docsInstances.js');

const DIRECTIVES = ['containerDirective', 'leafDirective', 'textDirective'];

/** Heading left with no text (at most a `{#id}`) once the other framework's content is removed */
Expand Down
5 changes: 4 additions & 1 deletion website/framework-docs/skillReferences.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@
import fs from 'node:fs';
import path from 'node:path';

import { frameworkInstance } from './docsInstances.js';
import { docToMarkdown, routeOf } from './docsToMarkdown.mjs';
import { ROOT, SITE, rel } from './site.mjs';

const SKILLS = path.join(ROOT, '.agents/skills');
const MANIFEST = 'references.json';
const HEADER = '<!-- Generated by `yarn build:skills` from';
const MD = /\.mdx?$/;
const REACT_ROUTE = frameworkInstance('react').routeBasePath;
const VUE_ROUTE = frameworkInstance('vue').routeBasePath;

/**
* Content ignoring the header, which framework's docs links point to, and the
Expand All @@ -29,7 +32,7 @@ const MD = /\.mdx?$/;
const comparable = content =>
content
?.replace(/^.*\n/, '')
.replaceAll(`${SITE}/vue/`, `${SITE}/docs/`)
.replaceAll(`${SITE}/${VUE_ROUTE}/`, `${SITE}/${REACT_ROUTE}/`)
.replace(/ {2,}/g, ' ')
.replace(/-{3,}/g, '---');

Expand Down
Loading
Loading