Skip to content

Commit

Permalink
upgrade all unified dependencies
Browse files Browse the repository at this point in the history
- had to comment out usage of rehype-lint and unified-lint-rule since those weren't migrated yet
  • Loading branch information
RyanClementsHax committed Oct 27, 2023
1 parent d215d27 commit 1b01fdd
Show file tree
Hide file tree
Showing 14 changed files with 6,066 additions and 778 deletions.
3 changes: 2 additions & 1 deletion components/pages/posts/[slug]/Content/Callout/Callout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import c from 'classnames'
import s from './Callout.module.scss'
import { ElementSubstitution } from 'lib/pages/posts/[slug]/client'

const classToIconMap: Record<string, string> = {
info: 'ℹ️',
Expand All @@ -8,7 +9,7 @@ const classToIconMap: Record<string, string> = {
danger: '🚫'
}

export const Callout: React.FC<React.HTMLAttributes<HTMLElement>> = ({
export const Callout: ElementSubstitution<'aside'> = ({
children,
...props
}) => (
Expand Down
3 changes: 2 additions & 1 deletion components/pages/posts/[slug]/Content/Code/Code.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ElementSubstitution } from 'lib/pages/posts/[slug]/client'
import s from './Code.module.scss'
import c from 'classnames'

export const Code: React.FC<React.HTMLAttributes<HTMLElement>> = props => {
export const Code: ElementSubstitution<'code'> = props => {
return <code {...props} className={c(s.code, props.className)} />
}
25 changes: 17 additions & 8 deletions components/pages/posts/[slug]/Content/Content.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import Link from 'next/link'
import Image, { ImageProps } from 'next/image'
import { ComponentsWithoutNodeOptions } from 'rehype-react/lib/complex-types'
import c from 'classnames'
import { HastTree } from 'lib/content/posts/types'
import s from './Content.module.scss'
import { Code } from './Code'
import { Callout } from './Callout'
import { convertToReact } from 'lib/pages/posts/[slug]/client'
import {
convertToReact,
ElementSubstitution,
Options
} from 'lib/pages/posts/[slug]/client'

export const Content: React.FC<{
root: HastTree
Expand All @@ -21,9 +24,11 @@ export const Content: React.FC<{
)
}

const ContentImage: React.FC<
React.ImgHTMLAttributes<HTMLImageElement> & { 'data-blurdataurl'?: string }
> = ({ alt, 'data-blurdataurl': blurDataURL, ...props }) => (
const ContentImage: ElementSubstitution<'img'> = ({
alt,
'data-blurdataurl': blurDataURL,
...props
}: JSX.IntrinsicElements['img'] & { 'data-blurdataurl'?: string }) => (
<Image
{...(props as ImageProps)}
alt={alt as string}
Expand All @@ -35,7 +40,11 @@ const ContentImage: React.FC<
/>
)

const Anchor: React.FC<React.AnchorHTMLAttributes<HTMLAnchorElement>> = props =>
const Anchor: ElementSubstitution<'a'> = ({
// eslint-disable-next-line @typescript-eslint/no-unused-vars
ref: notNeededAndCausesTSErrors,
...props
}) =>
props.href?.startsWith('#') ? (
<a {...props} />
) : (
Expand All @@ -47,15 +56,15 @@ const Anchor: React.FC<React.AnchorHTMLAttributes<HTMLAnchorElement>> = props =>
/>
)

const Video: React.FC<React.VideoHTMLAttributes<HTMLVideoElement>> = props => (
const Video: ElementSubstitution<'video'> = props => (
<video
controls
{...props}
className={c(props.className, 'w-full rounded-lg shadow-md')}
/>
)

const components: ComponentsWithoutNodeOptions['components'] = {
const components: Options['components'] = {
img: ContentImage,
code: Code,
aside: Callout,
Expand Down
2 changes: 1 addition & 1 deletion lib/content/posts/code/rehypeHighlightCodeBlocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const rehypeHighlightCodeBlocks: Plugin<[], HastElement> =
const starryNight = await getStarryNight()

visit(tree, { type: 'element', tagName: 'pre' }, (node, index, parent) => {
if (!parent || index === null) {
if (!parent || index === undefined) {
return
}

Expand Down
2 changes: 1 addition & 1 deletion lib/content/posts/code/rehypeHighlightInlineCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const rehypeHighlightInlineCode: Plugin<[], HastTree> =
const starryNight = await getStarryNight()

visit(tree, { type: 'element', tagName: 'code' }, (node, index, parent) => {
if (isPreElement(parent) || !parent || index === null) {
if (isPreElement(parent) || !parent || index === undefined) {
return
}

Expand Down
2 changes: 1 addition & 1 deletion lib/content/posts/code/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const isMetaValid = (meta: unknown): meta is string =>
typeof meta === 'string'

export const isPreElement = (
node: HastTree | HastElement | null
node: HastTree | HastElement | undefined
): node is HastElement =>
!!node && node.type === 'element' && node.tagName === 'pre'

Expand Down
23 changes: 21 additions & 2 deletions lib/content/posts/frontMatter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import remarkParse from 'remark-parse'
import { unified, Plugin } from 'unified'
import { unified, Plugin, Processor } from 'unified'
import { Node } from 'unist'
import { stringify } from 'gray-matter'
import { PresetBuilder } from './presetBuilder'
Expand Down Expand Up @@ -77,5 +77,24 @@ const frontMatterProcessor = unified()
.use(remarkParse)
.use(frontMatterTransformer)
.use(function () {
this.Compiler = (_, file) => file.data.matter as Record<string, unknown>
// "this" typing is poor with unified
// this code works fine
// https://github.com/rehypejs/rehype-react/blob/93fac074e8e3447088ed2408282e9e089ea7b36c/lib/index.js#L21
const self = this as unknown as Processor<
undefined,
undefined,
undefined,
Node,
Record<string, unknown>
>
self.compiler = (_, file) => file.data.matter as Record<string, unknown>
} as Plugin<[], Node, Record<string, unknown>>)

// Allows for custom return types
// https://github.com/unifiedjs/unified/tree/b69689bba52a918d87aa62f295ccffa8d9aa8ef8#compileresultmap
declare module 'unified' {
interface CompileResultMap {
// Register a new result (value is used, key should match it).
frontMatter: Record<string, unknown>
}
}
2 changes: 1 addition & 1 deletion lib/content/posts/imageTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ const rehypeConvertTopLevelImagesToFigures: Plugin<[], HastTree> =

const rehypeVideo: Plugin<[], HastTree> = () => async tree => {
visit(tree, { type: 'element', tagName: 'img' }, (node, index, parent) => {
if (!parent || index === null) {
if (!parent || index === undefined) {
return
}
const src = node.properties?.src
Expand Down
23 changes: 21 additions & 2 deletions lib/content/posts/parsing.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { unified, Plugin } from 'unified'
import { unified, Plugin, Processor } from 'unified'
import remarkParse from 'remark-parse'
import remarkRehype from 'remark-rehype'
import rehypeRaw from 'rehype-raw'
Expand Down Expand Up @@ -32,5 +32,24 @@ const contentProcessor = unified()
.use(rehypeSlug)
.use(rehypeAutolinkHeadings, { behavior: 'wrap' })
.use(function () {
this.Compiler = tree => ({ tree })
// "this" typing is poor with unified
// this code works fine
// https://github.com/rehypejs/rehype-react/blob/93fac074e8e3447088ed2408282e9e089ea7b36c/lib/index.js#L21
const self = this as unknown as Processor<
undefined,
undefined,
undefined,
HastTree,
{ tree: HastTree }
>
self.compiler = tree => ({ tree })
} as Plugin<[], HastTree, { tree: HastTree }>)

// Allows for custom return types
// https://github.com/unifiedjs/unified/tree/b69689bba52a918d87aa62f295ccffa8d9aa8ef8#compileresultmap
declare module 'unified' {
interface CompileResultMap {
// Register a new result (value is used, key should match it).
parsedHast: { tree: HastTree }
}
}
11 changes: 6 additions & 5 deletions lib/content/posts/presetBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { Preset, Plugin, PluginTuple } from 'unified'
import { Preset, Plugin } from 'unified'
import { Node } from 'unist'

export class PresetBuilder {
#plugins: PluginTuple[] = []
#plugins: NonNullable<Preset['plugins']> = []

public use<
PluginParameters extends unknown[] = unknown[],
Input = Node,
Input extends Node | string | undefined = Node,
Output = Input
>(
plugin: Plugin<PluginParameters, Input, Output>,
...settings: PluginParameters | [boolean]
...settings: PluginParameters
): this {
this.#plugins.push([
plugin as Plugin<unknown[], unknown, unknown>,
plugin as Plugin<unknown[], undefined, unknown>,
...settings
])
return this
Expand Down
65 changes: 36 additions & 29 deletions lib/content/posts/validation.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { unified } from 'unified'
import remarkParse from 'remark-parse'
import remarkStringify from 'remark-stringify'
import { lintRule } from 'unified-lint-rule'
import { visit } from 'unist-util-visit'
// import { lintRule } from 'unified-lint-rule'
// import { visit } from 'unist-util-visit'
import { reporter } from 'vfile-reporter'
import { pointStart } from 'unist-util-position'
import remarkLint from 'remark-lint'
import { Heading } from 'mdast'
// import { pointStart } from 'unist-util-position'
// import remarkLint from 'remark-lint'
// import { Heading } from 'mdast'
import { PresetBuilder } from './presetBuilder'

/**
Expand All @@ -16,41 +16,48 @@ import { PresetBuilder } from './presetBuilder'
* https://github.com/remarkjs/remark-lint/blob/cabbe86d1bd12ab5120196474ff08731644142d3/packages/remark-lint/index.js
*/

const remarkLintNoH1 = lintRule('remark-lint:no-h1', (tree, file) => {
visit(tree, { type: 'heading', depth: 1 }, node => {
file.fail(
"Don't use h1 headings (e.g. '# Example') as this messes up a11y because the post's tile will be the h1",
pointStart(node)
)
})
})
// const remarkLintNoH1 = lintRule('remark-lint:no-h1', (tree, file) => {
// visit(tree, { type: 'heading', depth: 1 }, node => {
// file.fail(
// "Don't use h1 headings (e.g. '# Example') as this messes up a11y because the post's tile will be the h1",
// pointStart(node)
// )
// })
// })

const remarkLintNoDeeperThanH3 = lintRule(
'remark-lint:no-deeper-than-h3',
(tree, file) => {
visit(tree, 'heading', (node: Heading) => {
if (node.depth > 3) {
file.fail(
"Don't use headings deeper than h3 (e.g. '#### This is a level 4 heading (h4)') as this can easily confuse readers and make the content harder to follow",
pointStart(node)
)
}
})
}
)
// const remarkLintNoDeeperThanH3 = lintRule(
// 'remark-lint:no-deeper-than-h3',
// (tree, file) => {
// visit(tree, 'heading', (node: Heading) => {
// if (node.depth > 3) {
// file.fail(
// "Don't use headings deeper than h3 (e.g. '#### This is a level 4 heading (h4)') as this can easily confuse readers and make the content harder to follow",
// pointStart(node)
// )
// }
// })
// }
// )

// TODO: reenable when lint packages are migrated
// they're currently on unified v10
// https://github.com/remarkjs/remark-lint/blob/53e9213c8950eff293846c03c253d7a331d318ce/packages/remark-lint/package.json#L41
// https://github.com/remarkjs/remark-lint/blob/53e9213c8950eff293846c03c253d7a331d318ce/packages/unified-lint-rule/package.json#L39
//
// Also attempt to remove @types/hast and vfile as direct dependencies

// docs on how to configure
// https://github.com/remarkjs/remark-lint
// example preset
// https://github.com/remarkjs/remark-lint/blob/cabbe86d1bd12ab5120196474ff08731644142d3/packages/remark-preset-lint-consistent/index.js
const postQualityPreset = new PresetBuilder()
.use(remarkLintNoH1, ['error'])
.use(remarkLintNoDeeperThanH3, ['error'])
// .use(remarkLintNoH1, ['error'])
// .use(remarkLintNoDeeperThanH3, ['error'])
.build()

const validator = unified()
.use(remarkParse)
.use(remarkLint)
// .use(remarkLint)
.use(postQualityPreset)
.use(remarkStringify)

Expand Down
23 changes: 17 additions & 6 deletions lib/pages/posts/[slug]/client.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
import { createElement, Fragment } from 'react'
import rehypeReact from 'rehype-react'
import { ComponentsWithoutNodeOptions } from 'rehype-react/lib/complex-types'
import { unified } from 'unified'
import rehypeReact, {
Options as ReactRehypeOptions,
Components
} from 'rehype-react'
import { HastTree } from 'lib/content/posts/types'
import * as prod from 'react/jsx-runtime'
import { unified } from 'unified'

// @ts-expect-error: the react types are missing.
// https://github.com/rehypejs/rehype-react/tree/93fac074e8e3447088ed2408282e9e089ea7b36c#use
const production = { Fragment: prod.Fragment, jsx: prod.jsx, jsxs: prod.jsxs }

export type Options = ReactRehypeOptions

export type ElementSubstitution<Element extends keyof Components> =
Components[Element]

export const convertToReact = (
content: HastTree,
components: ComponentsWithoutNodeOptions['components']
components: ReactRehypeOptions['components']
): React.ReactNode =>
unified()
.use(rehypeReact, { createElement, Fragment, components })
.use(rehypeReact, { ...production, components })
.stringify(content)
Loading

0 comments on commit 1b01fdd

Please sign in to comment.