Skip to content

Commit

Permalink
Add types to .mdx and other imports
Browse files Browse the repository at this point in the history
  • Loading branch information
wooorm committed Sep 29, 2021
1 parent 421b141 commit 59701d5
Show file tree
Hide file tree
Showing 17 changed files with 200 additions and 38 deletions.
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,10 @@ coverage/
node_modules/
giant.mdx
yarn.lock
*.d.ts
lib/**/*.d.ts
script/**/*.d.ts
test/**/*.d.ts
esbuild.d.ts
esm-loader.d.ts
rollup.d.ts
index.d.ts
61 changes: 61 additions & 0 deletions complex-types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
type PropsWithChildren<P> = P & JSX.ElementChildrenAttribute

type ComponentClass<P = Record<string, unknown>> = new (
props: PropsWithChildren<P>
) => any

type FunctionComponent<P = Record<string, unknown>> = (
props: PropsWithChildren<P>
) => any

type ComponentType<P = Record<string, unknown>> =
| ComponentClass<P>
| FunctionComponent<P>

// Allow one level of nesting.
export type Components = Record<
string,
| keyof JSX.IntrinsicElements
| ComponentType<any>
| Record<string, ComponentType>
> &
Partial<{
[TagName in keyof JSX.IntrinsicElements]:
| keyof JSX.IntrinsicElements
| ComponentType<JSX.IntrinsicElements[TagName]>
// Nested components are weird for valid intrinsics, but, there are many
// HTML element names, such as `map`, which are unlikely to be used as
// the element, and quite likely to be used as a component map instead.
| Record<string, ComponentType<any>>
}>

/**
* Props passed to the `MdxContent` component.
* Could be anything.
* The `components` prop is special: it defines what to use for components
* inside the content.
*/
// type-coverage:ignore-next-line
export type MdxContentProps = {[props: string]: any; components?: Components}

/**
* An function component which renders the MDX content using a JSX implementation.
*
* @param props
* Props passed to the `MdxContent` component.
* Could be anything.
* The `components` prop is special: it defines what to use for components
* inside the content.
* @returns
* A JSX element.
* The meaning of this may depend on the project configuration.
* As in, it could be a React, Preact, or Vue element.
*/
export type MdxContent = (props: MdxContentProps) => JSX.Element | null

/**
* An MDX module.
* Exports could be anything.
* The default export is an `MdxContent` component.
*/
export type MdxModule = {[identifier: string]: unknown; default: MdxContent}
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/// <reference types="./registry" />
export {createProcessor} from './lib/core.js'
export {compile, compileSync} from './lib/compile.js'
export {evaluate, evaluateSync} from './lib/evaluate.js'
Expand Down
11 changes: 4 additions & 7 deletions lib/evaluate.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
/**
* @typedef {import('vfile').VFileCompatible} VFileCompatible
* @typedef {import('./util/resolve-evaluate-options.js').EvaluateOptions} EvaluateOptions
*
* @typedef {{[name: string]: any}} ComponentMap
* @typedef {{[props: string]: any, components?: ComponentMap}} MDXContentProps
* @typedef {(props: MDXContentProps) => any} MDXContent
* @typedef {{[exports: string]: unknown, default: MDXContent}} ExportMap
* @typedef {import('../complex-types').MdxModule} MdxModule
* @typedef {import('../complex-types').MdxContent} MdxContent
*/

import {compile, compileSync} from './compile.js'
Expand All @@ -17,7 +14,7 @@ import {resolveEvaluateOptions} from './util/resolve-evaluate-options.js'
*
* @param {VFileCompatible} vfileCompatible MDX document to parse (`string`, `Buffer`, `vfile`, anything that can be given to `vfile`)
* @param {EvaluateOptions} evaluateOptions
* @return {Promise<ExportMap>}
* @return {Promise<MdxModule>}
*/
export async function evaluate(vfileCompatible, evaluateOptions) {
const {compiletime, runtime} = resolveEvaluateOptions(evaluateOptions)
Expand All @@ -31,7 +28,7 @@ export async function evaluate(vfileCompatible, evaluateOptions) {
*
* @param {VFileCompatible} vfileCompatible MDX document to parse (`string`, `Buffer`, `vfile`, anything that can be given to `vfile`)
* @param {EvaluateOptions} evaluateOptions
* @return {ExportMap}
* @return {MdxModule}
*/
export function evaluateSync(vfileCompatible, evaluateOptions) {
const {compiletime, runtime} = resolveEvaluateOptions(evaluateOptions)
Expand Down
6 changes: 5 additions & 1 deletion lib/util/resolve-evaluate-options.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
/**
* @typedef {import('../core.js').ProcessorOptions} ProcessorOptions
*
* @typedef RunnerOptions
* @typedef RuntimeOptions
* @property {*} Fragment Symbol to use for fragments
* @property {*} jsx Function to generate an element with static children
* @property {*} jsxs Function to generate an element with dynamic children
*
* @typedef ProviderOptions
* @property {*} [useMDXComponents] Function to get `MDXComponents` from context
*
* @typedef {RuntimeOptions & ProviderOptions} RunnerOptions
*
* @typedef {Omit<ProcessorOptions, 'jsx' | 'jsxImportSource' | 'jsxRuntime' | 'pragma' | 'pragmaFrag' | 'pragmaImportSource' | 'providerImportSource' | 'outputFormat'> } EvaluateProcessorOptions
*
* @typedef {EvaluateProcessorOptions & RunnerOptions} EvaluateOptions
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
"types": "./index.d.ts",
"files": [
"lib/",
"complex-types.d.ts",
"registry.d.ts",
"esbuild.js",
"esbuild.d.ts",
"esm-loader.js",
Expand Down Expand Up @@ -107,7 +109,7 @@
},
"scripts": {
"prepack": "npm run build && npm run format",
"build": "rimraf \"{lib/**/**,test/**/**,script/**,}*.d.ts\" && tsc && type-coverage",
"build": "rimraf \"{lib/**/**,test/**/**,script/**}*.d.ts\" \"{esbuild,esm-loader,index,rollup}.d.ts\" && tsc && type-coverage",
"format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
"test-api": "node --no-warnings --experimental-loader=./esm-loader.js test/index.js && node test/register.cjs",
"test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov node --no-warnings --experimental-loader=./esm-loader.js test/index.js",
Expand Down
89 changes: 89 additions & 0 deletions registry.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
declare module '*.mdx' {
/**
* MDX Content.
*
* @see https://v2.mdxjs.com/mdx/
*/
declare const Content: import('./complex-types').MdxContent
export default Content
}

declare module '*.md' {
/**
* Markdown content.
*
* @see https://spec.commonmark.org
*/
declare const Content: import('./complex-types').MdxContent
export default Content
}

declare module '*.markdown' {
/**
* Markdown content.
*
* @see https://spec.commonmark.org
*/
declare const Content: import('./complex-types').MdxContent
export default Content
}

declare module '*.mdown' {
/**
* Markdown content.
*
* @see https://spec.commonmark.org
*/
declare const Content: import('./complex-types').MdxContent
export default Content
}

declare module '*.mkdn' {
/**
* Markdown content.
*
* @see https://spec.commonmark.org
*/
declare const Content: import('./complex-types').MdxContent
export default Content
}

declare module '*.mkd' {
/**
* Markdown content.
*
* @see https://spec.commonmark.org
*/
declare const Content: import('./complex-types').MdxContent
export default Content
}

declare module '*.mdwn' {
/**
* Markdown content.
*
* @see https://spec.commonmark.org
*/
declare const Content: import('./complex-types').MdxContent
export default Content
}

declare module '*.mkdown' {
/**
* Markdown content.
*
* @see https://spec.commonmark.org
*/
declare const Content: import('./complex-types').MdxContent
export default Content
}

declare module '*.ron' {
/**
* Markdown content.
*
* @see https://spec.commonmark.org
*/
declare const Content: import('./complex-types').MdxContent
export default Content
}
4 changes: 2 additions & 2 deletions test/babel.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* @typedef {import('react').FC} FC
* @typedef {import('@babel/parser').ParserOptions} ParserOptions
* @typedef {import('estree-jsx').Program} Program
* @typedef {import('../complex-types').MdxContent} MdxContent
*/

import {promises as fs} from 'node:fs'
Expand Down Expand Up @@ -30,7 +30,7 @@ test('xdm (babel)', async (t) => {

await fs.writeFile(path.join(base, 'babel.js'), js)

const Content = /** @type {FC} */ (
const Content = /** @type {MdxContent} */ (
/* @ts-expect-error file is dynamically generated */
(await import('./context/babel.js')).default // type-coverage:ignore-line
)
Expand Down
22 changes: 12 additions & 10 deletions test/core.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/**
* @typedef {import('hast').Root} Root
* @typedef {import('../lib/compile.js').VFileCompatible} VFileCompatible
* @typedef {import('../lib/evaluate.js').MDXContent} MDXContent
* @typedef {import('../lib/evaluate.js').ExportMap} ExportMap
* @typedef {import('../complex-types').MdxContent} MdxContent
* @typedef {import('../complex-types').MdxModule} MdxModule
* @typedef {import('../complex-types').Components} Components
*/

import path from 'node:path'
Expand Down Expand Up @@ -115,7 +116,8 @@ test('xdm', async (t) => {

t.equal(
render(
h(await run(compileSync('?', {jsxImportSource: 'preact/compat'})), {})
// @ts-expect-error: Preact types do not accept `JSX.Element`.
h(await run(compileSync('?', {jsxImportSource: 'preact/compat'})), {}, [])
),
'<p>?</p>',
'should support an import source (`@jsxImportSource`)'
Expand All @@ -124,6 +126,7 @@ test('xdm', async (t) => {
t.equal(
render(
h(
// @ts-expect-error: Preact types do not accept `JSX.Element`.
await run(
compileSync('<>%</>', {
jsxRuntime: 'classic',
Expand All @@ -142,6 +145,7 @@ test('xdm', async (t) => {
t.equal(
render(
h(
// @ts-expect-error: Preact types do not accept `JSX.Element`.
await run(compileSync('<>1</>', {jsxImportSource: 'preact'}), {
keepImport: true
}),
Expand Down Expand Up @@ -202,7 +206,7 @@ test('xdm', async (t) => {
React.createElement(await run(compileSync('<x.y />')), {
components: {
x: {
/** @param {Object.<string, unknown>} props */
/** @param {object} props */
y(props) {
return React.createElement('span', props, '?')
}
Expand Down Expand Up @@ -239,7 +243,6 @@ test('xdm', async (t) => {
renderToStaticMarkup(
React.createElement(await run(compileSync('*a*')), {
components: {
/** @param {Object.<string, unknown>} props */
em(props) {
return React.createElement('i', props)
}
Expand All @@ -258,7 +261,6 @@ test('xdm', async (t) => {
),
{
components: {
/** @param {Object.<string, unknown>} props */
em(props) {
return React.createElement('i', props)
}
Expand Down Expand Up @@ -538,7 +540,7 @@ test('xdm', async (t) => {
)

try {
// @ts-expect-error runtime.
// @ts-expect-error runtime does not accept `detect`.
createProcessor({format: 'detect'})
t.fail()
} catch (/** @type {unknown} */ error) {
Expand Down Expand Up @@ -1318,7 +1320,7 @@ test('theme-ui', async (t) => {
*
* @param {VFileCompatible} input
* @param {{keepImport?: boolean}} [options]
* @return {Promise<MDXContent>}
* @return {Promise<MdxContent>}
*/
async function run(input, options = {}) {
return (await runWhole(input, options)).default
Expand All @@ -1328,7 +1330,7 @@ async function run(input, options = {}) {
*
* @param {VFileCompatible} input
* @param {{keepImport?: boolean}} [options]
* @return {Promise<ExportMap>}
* @return {Promise<MdxModule>}
*/
async function runWhole(input, options = {}) {
const name = 'fixture-' + nanoid().toLowerCase() + '.js'
Expand All @@ -1347,7 +1349,7 @@ async function runWhole(input, options = {}) {
await fs.writeFile(fp, doc)

try {
/** @type {ExportMap} */
/** @type {MdxModule} */
return await import('./context/' + name)
} finally {
// This is not a bug: the `finally` runs after the whole `try` block, but
Expand Down
4 changes: 2 additions & 2 deletions test/esbuild.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/**
* @typedef {import('react').FC} FC
* @typedef {import('esbuild').BuildFailure} BuildFailure
* @typedef {import('esbuild').Message} Message
* @typedef {import('unist').Parent} Parent
* @typedef {import('vfile').VFile} VFile
* @typedef {import('../complex-types').MdxContent} MdxContent
*/

import {promises as fs} from 'node:fs'
Expand Down Expand Up @@ -33,7 +33,7 @@ test('xdm (esbuild)', async (t) => {
plugins: [esbuildXdm()]
})

/** @type {FC} */
/** @type {MdxContent} */
let Content =
/* @ts-expect-error file is dynamically generated */
(await import('./context/esbuild.js')).default // type-coverage:ignore-line
Expand Down
5 changes: 2 additions & 3 deletions test/esm-loader.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* @typedef {import('react').FC} FC
* @typedef {import('../complex-types').MdxContent} MdxContent
*/

import path from 'node:path'
Expand All @@ -16,11 +16,10 @@ test('xdm (ESM loader)', async (t) => {
'export const Message = () => <>World!</>\n\n# Hello, <Message />'
)

/** @type {FC} */
/** @type {MdxContent} */
let Content

try {
/* @ts-expect-error file is dynamically generated */
Content = (await import('./context/esm-loader.mdx')).default // type-coverage:ignore-line
} catch (error) {
const exception = /** @type {NodeJS.ErrnoException} */ (error)
Expand Down
Loading

0 comments on commit 59701d5

Please sign in to comment.