Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose Shiki and Remark #69

Merged
merged 7 commits into from
Jun 8, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
2 changes: 1 addition & 1 deletion apps/create-guider/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@neato/create-guider",
"version": "1.0.0",
"version": "1.0.1",
"description": "Beautiful documentation sites, without all the hassle",
"main": "./entry.js",
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion apps/create-guider/templates/main/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"lint": "next lint"
},
"dependencies": {
"@neato/guider": "^1.0.3",
"@neato/guider": "^1.1.0",
"next": "^14.2.3",
"react": "^18.3.1",
"react-dom": "^18.3.1"
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/pages/docs/config/api/errors.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Error handling

Configuration loading has some potentionally confusing error handling.
By default, it will use the "pretty" assertion mode. This mode **will call process.exit(1)** instead of throwing errors. That means that you aren't able to catch it with a try-catch block.
By default, it will use the "pretty" assertion mode. **This "pretty" mode will call process.exit(1)** instead of throwing errors. That means that you aren't able to catch it with a try-catch block.

## The modes
- `pretty` Log the errors in pretty formatting and colors, then exit the process.
Expand Down
8 changes: 4 additions & 4 deletions apps/docs/pages/docs/config/api/utils.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Some helpers that can help you make configuration easier.

## `zodCoercedBoolean()`

Since `zod` boolean coercion is just doing `Boolean(value)`. You get weird cases like these:
Since `z.coerce.boolean()` is just doing `Boolean(value) under the hood. You get weird cases like these:
- `z.coerce.boolean().parse("true"); // => true`;
- `z.coerce.boolean().parse("false"); // => true`;
- `z.coerce.boolean().parse("yes"); // => true`;
Expand All @@ -14,9 +14,9 @@ Since `zod` boolean coercion is just doing `Boolean(value)`. You get weird cases
If you use `zodCoercedBoolean()` instead of `z.boolean()`, things will be more as you expect them to be.
Here is a list of valid values, these are checked while ignoring casing (if its a string):
- `"true"` -> `true`
- `"false"` -> `false`
- `"yes"` -> `true`
- `"no"` -> `true`
- `true` -> `true`
- `false` -> `true`
- `"false"` -> `false`
- `"no"` -> `false`
- `false` -> `false`
- any other values will be read as `false`
1 change: 0 additions & 1 deletion apps/docs/pages/docs/config/guide/basic-example.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ See the code snippet below for a usual setup in projects
.addFromFile(".env") // loads PORT=8080 from file
.addFromFile(".json") // loads { "port": 8080 } from file
.addZodSchema(schema) // validates the loaded data to make sure it follow the schema
.freeze() // freezes the object so no changes can be made at runtime
.load(); // this returns the fully type configuration (type infered from schema)
```
</CodeGroup.Code>
Expand Down
56 changes: 56 additions & 0 deletions apps/docs/pages/docs/guider/api-reference/setup/guider.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# `guider()`

This function is the way you inject Guider into NextJS. You can use its options
to change how Guider behaves in the compile step.


## Example

```tsx title="/next.config.mjs"
import { guider } from "@neato/guider";

const withGuider = guider({
themeConfig: './theme.config.tsx',
});

export default withGuider({
output: 'export',
});
```


## Reference

```tsx
function guider(options);
```

<Field title="options" type="DirectoryOptions" required>
Options for the redirect.

<Field.Properties defaultOpen>
<Field title="themeConfig" type="string" required>
The location of the theme file, the theme file is used to configure almost everything about guider.
</Field>
<Field title="extraRemarkPlugins" type="RemarkPlugin[]">
A list where you can specify [remark plugins](https://github.com/remarkjs/remark/blob/main/doc/plugins.md) to use on top of the default list of plugins.

Use it to add new features to your markdown files.
</Field>
<Field title="remarkPlugins" type="RemarkPlugin[]">
A list where you can specify [remark plugins](https://github.com/remarkjs/remark/blob/main/doc/plugins.md) to use **instead of the default Guider plugins**.

Use it to overwrite how markdown parsing works. **Be aware that this will disable a lot of the default features of Guider, use at your own risk.** Use `extraRemarkPlugins` instead if you want to add extra plugins without breaking existing functionality.
</Field>
<Field title="extraShikiTransformers" type="ShikiTransformer[]">
A list where you can specify [Shiki transformers](https://shiki.style/guide/transformers) to use on top of the default transformers.

Use it to add new features to syntax highlighting in codeblocks.
</Field>
<Field title="shikiTransformers" type="ShikiTransformer[]">
A list where you can specify [Shiki transformers](https://shiki.style/guide/transformers) to use **instead of the default Guider transformers**.

Use it to overwrite how syntax highlighting works in Guider. **Be aware that this will disable a lot of the default features of Guider, use at your own risk.** Use `extraShikiTransformers` instead if you want to add extra transformers without breaking existing functionality.
</Field>
</Field.Properties>
</Field>
16 changes: 0 additions & 16 deletions apps/docs/pages/showcase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import { Showcase } from 'components/showcase-layout';
import type { ShowcaseTag, ShowcaseType } from 'components/showcase-card';
import { ShowcaseCard, ShowcaseCardContainer } from 'components/showcase-card';
import pretendoImg from 'public/showcases/pretendo.png';
import mwAccountImg from 'public/showcases/movie-web-account.png';
import mwDocsImg from 'public/showcases/movie-web-docs.png';

const showcases: ShowcaseType[] = [
{
Expand All @@ -15,20 +13,6 @@ const showcases: ShowcaseType[] = [
imageUrl: pretendoImg.src,
tags: ['guider'],
},
{
title: 'movie-web backend',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rip

description: 'Uses Config for their account service.',
href: 'https://github.com/movie-web/backend/',
imageUrl: mwAccountImg.src,
tags: ['config'],
},
{
title: 'movie-web docs',
description: 'Uses Guider for their documentation',
href: 'https://github.com/movie-web/docs/',
imageUrl: mwDocsImg.src,
tags: ['guider'],
},
];

export default function ShowcasePage() {
Expand Down
Binary file removed apps/docs/public/showcases/movie-web-account.png
Binary file not shown.
Binary file removed apps/docs/public/showcases/movie-web-docs.png
Binary file not shown.
2 changes: 2 additions & 0 deletions apps/docs/theme.config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,8 @@ export default defineTheme([
link('Layout settings', gdApi('/theme/settings')),
]),

group('Setup', [link('guider()', gdApi('/setup/guider'))]),

group('_meta.json', [
link('Structure of _meta.json', gdApi('/meta/structure')),
]),
Expand Down
2 changes: 1 addition & 1 deletion packages/guider/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@neato/guider",
"version": "1.0.3",
"version": "1.1.0",
"description": "Beautiful documentation sites, without all the hassle",
"main": "./dist/index.js",
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion packages/guider/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export function guider(initConfig: GuiderInitConfig) {
...initConfig,
};
const guiderPlugin = new GuiderPlugin(guiderConfig);
const searchPlugin = new GuiderSearchPlugin();
const searchPlugin = new GuiderSearchPlugin(guiderConfig);

function withGuider(nextConfig: NextConfig = {}): NextConfig {
const extraWatchers = new ExtraWatchWebpackPlugin({
Expand Down
27 changes: 27 additions & 0 deletions packages/guider/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,30 @@
import type { ShikiTransformer } from 'shiki';
import type { PluggableList } from 'unified';

export type GuiderInitConfig = {
/**
* The location of the theme config
*/
themeConfig: string;

/**
* Extra shiki transformers added to syntax highlighted code
*/
extraShikiTransformers?: ShikiTransformer[];

/**
* Replaces all shiki transformers added to syntax highlighted code
*/
shikiTransformers?: ShikiTransformer[];

/**
* Extra remark plugins added to markdown parsing
*/
extraRemarkPlugins?: PluggableList;

/**
* Replaces all remark plugins added to markdown parsing
* NOTE: This disables and breaks a lot of features of Guider, use at your own risk.
*/
remarkPlugins?: PluggableList;
};
6 changes: 5 additions & 1 deletion packages/guider/src/webpack/loader/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ async function loader(
if (directories.pagesDir) context.addContextDependency(directories.pagesDir);

if (type === 'virtual') return virtualLoader(getGuiderPluginCache());
if (type === 'mdx') return (await mdLoader(source)).script;
if (type === 'mdx') {
if (!guiderConfig)
throw new Error('Could not read config for markdown loader');
return (await mdLoader(source, guiderConfig)).script;
}

throw new Error(`Loader used with incorrect type (${type})`);
}
Expand Down
117 changes: 62 additions & 55 deletions packages/guider/src/webpack/loader/md-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import remarkHeadingId from 'remark-heading-id';
import grayMatter from 'gray-matter';
import rehypePrettyCode from 'rehype-pretty-code';
import rehypeExtractExcerpt from 'rehype-extract-excerpt';

Check warning on line 8 in packages/guider/src/webpack/loader/md-loader.ts

View workflow job for this annotation

GitHub Actions / Linting and Typecheck

Using exported name 'rehypeExtractExcerpt' as identifier for default export
import remarkLinkRewrite from 'remark-link-rewrite';
import { remarkNpm2Yarn } from '@theguild/remark-npm2yarn';
import remarkGfm from 'remark-gfm';
Expand All @@ -16,74 +16,81 @@
transformerNotationWordHighlight,
transformerNotationErrorLevel,
} from '@shikijs/transformers';
import type { GuiderInitConfig } from 'src/types';
import { remarkSearchData } from './search-data';

const EXPORT_FOOTER = 'export default ';

export async function mdLoader(source: string) {
export async function mdLoader(source: string, config: GuiderInitConfig) {
const meta = grayMatter(source);
const file = await compile(source, {
jsx: true,
outputFormat: 'program',
format: 'detect',
providerImportSource: '@neato/guider/client',
remarkPlugins: [
remarkFrontmatter,
[remarkHeadingId, { defaults: true }],
remarkHeadings,
[
remarkNpm2Yarn,
{
packageName: '@neato/guider/client',
tabNamesProp: 'items',
storageKey: '__guider_packageManager',
},
],
remarkGfm,
[
remarkLinkRewrite,
{
replacer: (url: string) => {
const hasProtocol = Boolean(url.match(/[a-zA-Z]+:/g));
if (hasProtocol) return url;

const [path, hash] = url.split('#', 2);

const pathSections = path.split('/');
const lastSectionIndex = pathSections.length - 1;

// We get the last section so that only the last extension is removed
// e.g. bar.ts.mdx -> bar.ts
const lastDot = pathSections[lastSectionIndex].lastIndexOf('.');

// If there is no dot, there is no extension to remove so we can return the url as is
if (lastDot === -1) return url;

pathSections[lastSectionIndex] = pathSections[
lastSectionIndex
].slice(0, lastDot);

const hashPath = hash && hash.length > 0 ? `#${hash}` : '';
return `${pathSections.join('/')}${hashPath}`;
},
},
],
remarkSearchData,
],
remarkPlugins: config.remarkPlugins
? config.remarkPlugins
: [
remarkFrontmatter,
[remarkHeadingId, { defaults: true }],
remarkHeadings,
[
remarkNpm2Yarn,
{
packageName: '@neato/guider/client',
tabNamesProp: 'items',
storageKey: '__guider_packageManager',
},
],
remarkGfm,
[
remarkLinkRewrite,
{
replacer: (url: string) => {
const hasProtocol = Boolean(url.match(/[a-zA-Z]+:/g));
if (hasProtocol) return url;

const [path, hash] = url.split('#', 2);

const pathSections = path.split('/');
const lastSectionIndex = pathSections.length - 1;

// We get the last section so that only the last extension is removed
// e.g. bar.ts.mdx -> bar.ts
const lastDot = pathSections[lastSectionIndex].lastIndexOf('.');

// If there is no dot, there is no extension to remove so we can return the url as is
if (lastDot === -1) return url;

pathSections[lastSectionIndex] = pathSections[
lastSectionIndex
].slice(0, lastDot);

const hashPath = hash && hash.length > 0 ? `#${hash}` : '';
return `${pathSections.join('/')}${hashPath}`;
},
},
],
remarkSearchData,
...(config.extraRemarkPlugins ?? []),
],
rehypePlugins: [
rehypeExtractExcerpt,
[
rehypePrettyCode,
{
defaultLang: 'txt',
keepBackground: false,
transformers: [
transformerNotationDiff(),
transformerNotationHighlight(),
transformerNotationFocus(),
transformerNotationWordHighlight(),
transformerNotationErrorLevel(),
],
transformers: config.shikiTransformers
? config.shikiTransformers
: [
transformerNotationDiff(),
transformerNotationHighlight(),
transformerNotationFocus(),
transformerNotationWordHighlight(),
transformerNotationErrorLevel(),
...(config.extraShikiTransformers ?? []),
],
},
],
],
Expand All @@ -97,11 +104,11 @@

const pageOpts = {
meta: meta.data,
headings: file.data.headings,
excerpt: file.data.excerpt,
headings: file.data.headings ?? [],
excerpt: file.data.excerpt ?? '',
};

const firstHeading = (file.data.headings as Heading[]).find(
const firstHeading = ((file.data.headings ?? []) as Heading[]).find(
(h) => h.depth === 1,
);

Expand All @@ -121,7 +128,7 @@
return {
script,
searchData: {
sections: file.data.sections,
sections: file.data.sections ?? [],
pageTitle: meta.data?.title ?? firstHeading?.value ?? undefined,
},
};
Expand Down
Loading
Loading