Skip to content

Commit

Permalink
Typescript Type Definitions (#70)
Browse files Browse the repository at this point in the history
* add typing for hydrate

* refine types

* further refinement, break into separate files, update test to use ts

* 1.0.1-canary.0

* add type docs to readme
  • Loading branch information
jescalan committed Dec 23, 2020
1 parent 34e7748 commit 5b39c3b
Show file tree
Hide file tree
Showing 12 changed files with 3,200 additions and 1,042 deletions.
4 changes: 3 additions & 1 deletion .npmignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
__tests__
examples
.prettierrc
.prettierrc
tsconfig.json
babel.config.js
42 changes: 41 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,12 @@ import Test from '../components/test'
const components = { Test }
export default function TestPage({ renderedOutput }) {
return <div className="wrapper" dangerouslySetInnerHTML={{ __html: renderedOutput }} />
return (
<div
className="wrapper"
dangerouslySetInnerHTML={{ __html: renderedOutput }}
/>
)
}
export async function getStaticProps() {
Expand All @@ -196,6 +201,41 @@ export async function getStaticProps() {
}
```

## Typescript

This project does include native types for typescript use. Both `renderToString` and `hydrate` have types normally as you'd expect, and the library also offers exports of two types that are shared between the two functions and that you may need to include in your own files. Both types can be imported from `next-mdx-remote/types` and are namespaced under `MdxRemote`. The two types are as follows:

- `MdxRemote.Components` - represents the type of the "components" object referenced in the docs above, which needs to be passed to both `hydrate` and `renderToString`
- `MdxRemote.Source` - represents the type of the return value of `renderToString`, which also must be passed into `hydrate.
Below is an example of a simple implementation in typescript. You may not need to implement the types exactly in this way for every configuration of typescript - this example is just a demonstration of where the types could be applied if needed.
```ts
import renderToString from 'next-mdx-remote/render-to-string'
import hydrate from 'next-mdx-remote/hydrate'
import { MdxRemote } from 'next-mdx-remote/types'
import ExampleComponent from './example'

const components: MdxRemote.Components = { ExampleComponent }

interface Props {
mdxSource: MdxRemote.Source
}

export default function ExamplePage({ mdxSource }: Props) {
const content = hydrate(mdxSource, { components })
return <div>{content}</div>
}

export async function getStaticProps() {
const mdxSource = await renderToString(
'some *mdx* content: <ExampleComponent />',
{ components }
)
return { props: { mdxSource } }
}
```

## License

[Mozilla Public License Version 2.0](./LICENSE)
49 changes: 27 additions & 22 deletions __tests__/index.js → __tests__/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
const spawn = require('cross-spawn')
const path = require('path')
const fs = require('fs')
const puppeteer = require('puppeteer')
const handler = require('serve-handler')
const http = require('http')
const rmfr = require('rmfr')
const renderToString = require('../render-to-string')
const React = require('react')
const { paragraphCustomAlerts } = require('@hashicorp/remark-plugins')
import { Server } from 'http'
import { Browser } from 'puppeteer'

import spawn from 'cross-spawn'
import path from 'path'
import fs from 'fs'
import puppeteer from 'puppeteer'
import handler from 'serve-handler'
import http from 'http'
import rmfr from 'rmfr'
import renderToString from '../render-to-string'
import React from 'react'
import { paragraphCustomAlerts } from '@hashicorp/remark-plugins'

jest.setTimeout(30000)

Expand All @@ -20,27 +23,26 @@ test('rehydrates correctly in browser', () => {
'<h1>foo</h1><div><h1>Headline</h1><p>hello <!-- -->jeff</p><button>Count: <!-- -->0</button><p>Some <strong class="custom-strong">markdown</strong> content</p><div class="alert alert-warning g-type-body" role="alert"><p>Alert</p></div></div>'
)
// hydrates correctly
let browser, server
let browser: Browser, server: Server
return new Promise(async (resolve) => {
browser = await puppeteer.launch()
const page = await browser.newPage()
page.on('console', (msg) => console.log(msg.text()))
server = await serveStatic('basic')
await page.exposeFunction('__NEXT_HYDRATED_CB', async () => {
// click the button, flakes with one click for some reason
await page.click('button')
// click the button
await page.click('button')
// wait for react to render
await page.waitFor(() => {
return document.querySelector('button').innerText !== 'Count: 0'
return document.querySelector('button')?.innerHTML === 'Count: 1'
})
// pull the text for a test confirm
const buttonCount = page.$eval('button', (el) => el.innerText)
const buttonCount = page.$eval('button', (el) => el.innerHTML)
resolve(buttonCount)
})
await page.goto('http://localhost:1235', { waitUntil: 'domcontentloaded' })
}).then(async (buttonText) => {
expect(buttonText).not.toEqual('Count: 0')
expect(buttonText).toEqual('Count: 1')

// close the browser and dev server
await browser.close()
Expand Down Expand Up @@ -75,7 +77,10 @@ test('renderToString with options', async () => {

test('renderToString with scope', async () => {
const result = await renderToString('<Test name={bar} />', {
components: { Test: ({ name }) => React.createElement('p', null, name) },
components: {
Test: ({ name }: { name: string }) =>
React.createElement('p', null, name),
},
scope: {
bar: 'test',
},
Expand All @@ -92,27 +97,27 @@ afterAll(async () => {
// utility functions
//

function buildFixture(fixture) {
function buildFixture(fixture: string) {
spawn.sync('next', ['build'], {
stdio: 'inherit',
cwd: path.join(__dirname, 'fixtures', fixture),
env: { ...process.env, NODE_ENV: undefined, __NEXT_TEST_MODE: true },
env: { ...process.env, NODE_ENV: undefined, __NEXT_TEST_MODE: 'true' },
})
spawn.sync('next', ['export'], {
stdio: 'inherit',
cwd: path.join(__dirname, 'fixtures', fixture),
env: { ...process.env, NODE_ENV: undefined, __NEXT_TEST_MODE: true },
env: { ...process.env, NODE_ENV: undefined, __NEXT_TEST_MODE: 'true' },
})
}

function readOutputFile(fixture, name) {
function readOutputFile(fixture: string, name: string) {
return fs.readFileSync(
path.join(__dirname, 'fixtures', fixture, 'out', `${name}.html`),
'utf8'
)
}

function serveStatic(fixture) {
function serveStatic(fixture: string): Promise<Server> {
return new Promise((resolve) => {
const server = http.createServer((req, res) =>
handler(req, res, {
Expand Down
6 changes: 6 additions & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
presets: [
['@babel/preset-env', { targets: { node: 'current' } }],
'@babel/preset-typescript',
],
}
14 changes: 14 additions & 0 deletions hydrate.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ReactNode } from 'react'
import { MdxRemote } from 'types'

export default function hydrate(
/** Rendered MDX output. The direct output of `renderToString`. */
source: MdxRemote.Source,
/**
* A map of names to React components.
* The key used will be the name accessible to MDX.
*
* For example: `{ ComponentName: Component }` will be accessible in the MDX as `<ComponentName/>`.
*/
options: { components: MdxRemote.Components }
): ReactNode
10 changes: 10 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import hydrate from 'hydrate'
import renderToString from 'render-to-string'

declare module 'next-mdx-remote/render-to-string' {
export default renderToString
}

declare module 'next-mdx-remote/hydrate' {
export default hydrate
}
Loading

0 comments on commit 5b39c3b

Please sign in to comment.