Skip to content
Open
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
11 changes: 11 additions & 0 deletions .changeset/react-kitten-package.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@doc-kittens/react': major
'@node-core/doc-kit': minor
---

The React/JSX-based generators (`html` — previously `web` —, `jsx-ast`,
`llms-txt`, `sitemap`, and `orama-db`) now live in the new
`@doc-kittens/react` package and are loaded via import specifiers such as
`@doc-kittens/react/html`. The `web` generator is renamed to `html`: the CLI
shorthand `web` keeps working as a deprecated alias, but the configuration
key is now `html` instead of `web`.
2 changes: 1 addition & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export default defineConfig([
{
files: [
'packages/core/src/generators/legacy-html/assets/*.js',
'packages/core/src/generators/web/ui/**/*',
'packages/react/src/html/ui/**/*',
],
languageOptions: {
globals: {
Expand Down
52 changes: 36 additions & 16 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 0 additions & 21 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,12 @@
"./ast": "./src/generators/ast/index.mjs",
"./ast-js": "./src/generators/ast-js/index.mjs",
"./json-simple": "./src/generators/json-simple/index.mjs",
"./jsx-ast": "./src/generators/jsx-ast/index.mjs",
"./legacy-html": "./src/generators/legacy-html/index.mjs",
"./legacy-html-all": "./src/generators/legacy-html-all/index.mjs",
"./legacy-json": "./src/generators/legacy-json/index.mjs",
"./legacy-json-all": "./src/generators/legacy-json-all/index.mjs",
"./llms-txt": "./src/generators/llms-txt/index.mjs",
"./man-page": "./src/generators/man-page/index.mjs",
"./metadata": "./src/generators/metadata/index.mjs",
"./orama-db": "./src/generators/orama-db/index.mjs",
"./sitemap": "./src/generators/sitemap/index.mjs",
"./web": "./src/generators/web/index.mjs",
"./package.json": "./package.json",
"./shiki.config.mjs": "./shiki.config.mjs",
"./src/*": "./src/*",
Expand All @@ -54,33 +49,18 @@
],
"dependencies": {
"@actions/core": "^3.0.0",
"@fontsource-variable/open-sans": "^5.3.0",
"@fontsource/ibm-plex-mono": "^5.3.0",
"@heroicons/react": "^2.2.0",
"@node-core/rehype-shiki": "^1.4.2",
"@node-core/ui-components": "^1.7.2",
"@orama/orama": "^3.1.18",
"@orama/ui": "^1.5.4",
"@swc/html-wasm": "^1.15.43",
"@swc/wasm": "^1.15.46",
"acorn": "^8.17.0",
"commander": "^15.0.0",
"cosmiconfig": "^9.0.2",
"dedent": "^1.7.2",
"estree-util-to-js": "^2.0.0",
"estree-util-visit": "^2.0.0",
"github-slugger": "^2.0.0",
"glob-parent": "^6.0.2",
"hast-util-to-string": "^3.0.1",
"hastscript": "^9.0.1",
"mdast-util-slice-markdown": "^2.0.1",
"piscina": "^5.2.0",
"preact": "^10.29.2",
"preact-render-to-string": "^6.7.0",
"reading-time": "^1.5.0",
"recma-jsx": "^1.0.1",
"rehype-raw": "^7.0.0",
"rehype-recma": "^1.0.0",
"rehype-stringify": "^10.0.1",
"remark-gfm": "^4.0.1",
"remark-mdx": "^3.1.1",
Expand All @@ -97,7 +77,6 @@
"unist-util-remove": "^4.0.0",
"unist-util-select": "^5.1.0",
"unist-util-visit": "^5.1.0",
"vite": "~8.1.5",
"yaml": "^2.9.0"
}
}
25 changes: 23 additions & 2 deletions packages/core/src/generators/__tests__/index.test.mjs
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';

import { allGenerators, publicGenerators } from '../index.mjs';
import {
allGenerators,
deprecatedGenerators,
publicGenerators,
} from '../index.mjs';
import { loadGenerator, resolveGeneratorSpecifier } from '../loader.mjs';

const validDependencies = Object.values(allGenerators);

// Deprecated aliases intentionally have keys that differ from the
// generator's name, so they are excluded from the name-match assertions.
const currentGenerators = Object.fromEntries(
Object.entries(allGenerators).filter(
([name]) => !(name in deprecatedGenerators)
)
);

const loadedGenerators = await Promise.all(
Object.entries(allGenerators).map(async ([name, specifier]) => [
Object.entries(currentGenerators).map(async ([name, specifier]) => [
name,
specifier,
await loadGenerator(specifier),
Expand Down Expand Up @@ -50,6 +62,15 @@ describe('All Generators', () => {
});
});

it('should resolve deprecated aliases to loadable generators', async () => {
for (const [name, specifier] of Object.entries(deprecatedGenerators)) {
assert.equal(resolveGeneratorSpecifier(name), specifier);

const generator = await loadGenerator(specifier);
assert.ok(generator.name, `Deprecated alias "${name}" must load`);
}
});

it('should have ast generator as a top-level generator with no dependencies', async () => {
const ast = await loadGenerator(allGenerators.ast);
assert.ok(ast, 'ast generator should exist');
Expand Down
20 changes: 14 additions & 6 deletions packages/core/src/generators/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

/**
* Maps the shorthand names accepted by the CLI and configuration files
* (e.g. `--target web`) to the import specifiers they resolve to.
* (e.g. `--target html`) to the import specifiers they resolve to.
*
* Generators are loaded dynamically by specifier (see `./loader.mjs`), so this
* module must not import any generator code — it is purely a lookup table.
Expand All @@ -18,10 +18,10 @@ export const publicGenerators = {
'legacy-json-all': '@node-core/doc-kit/legacy-json-all',
'addon-verify': '@node-core/doc-kit/addon-verify',
'api-links': '@node-core/doc-kit/api-links',
'orama-db': '@node-core/doc-kit/orama-db',
'llms-txt': '@node-core/doc-kit/llms-txt',
sitemap: '@node-core/doc-kit/sitemap',
web: '@node-core/doc-kit/web',
'orama-db': '@doc-kittens/react/orama-db',
'llms-txt': '@doc-kittens/react/llms-txt',
sitemap: '@doc-kittens/react/sitemap',
html: '@doc-kittens/react/html',
};

// These ones are special since they don't produce standard output,
Expand All @@ -30,11 +30,19 @@ export const publicGenerators = {
const internalGenerators = {
ast: '@node-core/doc-kit/ast',
metadata: '@node-core/doc-kit/metadata',
'jsx-ast': '@node-core/doc-kit/jsx-ast',
'jsx-ast': '@doc-kittens/react/jsx-ast',
'ast-js': '@node-core/doc-kit/ast-js',
};

// Former names kept resolvable for existing invocations and config files.
// Unlike the maps above, keys here intentionally differ from the generator's
// `name` property.
export const deprecatedGenerators = {
web: '@doc-kittens/react/html',
};

export const allGenerators = {
...publicGenerators,
...internalGenerators,
...deprecatedGenerators,
};
22 changes: 0 additions & 22 deletions packages/core/src/generators/web/template.html

This file was deleted.

89 changes: 14 additions & 75 deletions packages/core/src/utils/remark.mjs
Original file line number Diff line number Diff line change
@@ -1,60 +1,30 @@
'use strict';

import rehypeShikiji from '@node-core/rehype-shiki/plugin';
import recmaJsx from 'recma-jsx';
import recmaStringify from 'recma-stringify';
import rehypeRaw from 'rehype-raw';
import rehypeRecma from 'rehype-recma';
import rehypeStringify from 'rehype-stringify';
import remarkGfm from 'remark-gfm';
import remarkMdx from 'remark-mdx';
import remarkParse from 'remark-parse';
import remarkRehype from 'remark-rehype';
import remarkStringify from 'remark-stringify';
import { unified } from 'unified';
import { visit } from 'unist-util-visit';

import syntaxHighlighter, { highlighter } from './highlighter.mjs';
import syntaxHighlighter from './highlighter.mjs';
import { lazy } from './misc.mjs';
import {
typeAnnotationToHast,
typeAnnotationToHighlightedHast,
} from './type-annotations/hast.mjs';
import { typeAnnotationToHast } from './type-annotations/hast.mjs';
import remarkTypeAnnotations from './type-annotations/remark.mjs';
import { AST_NODE_TYPES } from '../generators/jsx-ast/constants.mjs';
import transformAlerts from '../generators/jsx-ast/utils/plugins/alerts.mjs';
import transformElements from '../generators/jsx-ast/utils/plugins/transformer.mjs';

const passThrough = ['element', ...Object.values(AST_NODE_TYPES.MDX)];
const codeMetaProperty = 'codeMeta';

/**
* Stores fenced code metadata on properties before rehypeRaw reparses the tree.
*/
const preserveCodeMeta = () => tree => {
visit(tree, 'element', node => {
const meta = node.data?.meta;

if (node.tagName === 'code' && typeof meta === 'string') {
node.properties ||= {};
node.properties[codeMetaProperty] = meta;
}
});
};

/**
* Restores fenced code metadata so the Shiki plugin can read displayName.
*/
const restoreCodeMeta = () => tree => {
visit(tree, 'element', node => {
const meta = node.properties?.[codeMetaProperty];

if (node.tagName === 'code' && typeof meta === 'string') {
node.data = { ...node.data, meta };
delete node.properties[codeMetaProperty];
}
});
};
// MDX node types that may appear in trees parsed by `getRemarkMdx`; the
// rehype pipelines pass them through untouched.
const passThrough = [
'element',
'mdxJsxTextElement',
'mdxJsxFlowElement',
'mdxJsxAttribute',
'mdxJsxAttributeValueExpression',
'mdxFlowExpression',
'mdxTextExpression',
'mdxjsEsm',
];

/**
* Retrieves an instance of Remark configured to parse GFM (GitHub Flavored Markdown)
Expand Down Expand Up @@ -125,34 +95,3 @@ export const getRemarkRehypeWithShiki = lazy(() =>
// and we trust the sources of the Markdown files
.use(rehypeStringify, { allowDangerousHtml: true })
);

const singletonShiki = await rehypeShikiji({ highlighter });

/**
* Retrieves an instance of Remark configured to output JSX code.
* including parsing Code Boxes with syntax highlighting
*/
export const getRemarkRecma = lazy(() =>
unified()
.use(remarkParse)
.use(transformAlerts)
// We make Rehype ignore existing HTML nodes, and JSX nodes
// as these are nodes we manually created during the generation process
// We also allow dangerous HTML to be passed through, since we have HTML within our Markdown
// and we trust the sources of the Markdown files
.use(remarkRehype, {
allowDangerousHtml: true,
passThrough,
// The web pipeline gets Shiki-highlighted types with embedded links
handlers: { typeAnnotation: typeAnnotationToHighlightedHast },
})
.use(preserveCodeMeta)
// Any `raw` HTML in the markdown must be converted to AST in order for Recma to understand it
.use(rehypeRaw, { passThrough })
.use(restoreCodeMeta)
.use(() => singletonShiki)
.use(transformElements)
.use(rehypeRecma)
.use(recmaJsx)
.use(recmaStringify)
);
Loading
Loading