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

Implement first pass of MDXs #507

Closed
wants to merge 2 commits into from
Closed
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
76 changes: 76 additions & 0 deletions packages/remark-mdxs/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
const visit = require('unist-util-visit')
const remove = require('unist-util-remove')
const {toJSX} = require('@mdx-js/mdx/mdx-hast-to-jsx')

module.exports = function({delimiter = 'hr'}) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Just curious, I'm using thematicBreak in @mdx-deck/mdx-plugin but with the way the delimiter is used below will either hr or thematicBreak work?

Copy link
Member Author

Choose a reason for hiding this comment

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

As is it won't work because we're operating on MDXHAST (since it's acting as a custom compiler for MDX). But it might make sense to make MDXS more standalone and not use mdx core and remark-mdx directly instead. That would allow us to operate on the MDXAST.

Copy link
Contributor

@karlhorky karlhorky Dec 20, 2022

Choose a reason for hiding this comment

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

Regarding these architectural things, if remark-mdxs (or a different implementation of MDXs) were to be implemented today, do either of you (or @ChristianMurphy or @wooorm) have any recommendations for trying to make a new MDXs PR for MDX v2? Eg. either how / where the PR should be submitted, whether MDXs should be "more standalone" like described above, etc?

@EricCote or @ProchaLu (or both) may take a shot at implementing something like this in the next weeks, see the link below. The first idea was to take the implementation in this PR and upgrade it to use MDX v2 APIs.

this.Compiler = tree => {
const splits = []
const documents = []

const importNodes = tree.children.filter(n => n.type === 'import')
const exportNodes = tree.children.filter(n => n.type === 'export')

const layout = exportNodes.find(node => node.default)

// We don't care about imports and exports when handling
// multiple MDX documents
let mdxsTree = remove(remove(tree, 'export'), 'import')
const {children} = mdxsTree

visit(mdxsTree, node => {
if (node.tagName === delimiter) {
splits.push(children.indexOf(node))
}
})

let previousSplit = 0
for (let i = 0; i < splits.length; i++) {
const split = splits[i]
documents.push(children.slice(previousSplit, split))
previousSplit = split + 1
}

documents.push(children.slice(previousSplit))

const jsxFragments = documents
.map(nodes => nodes.map(toJSX).join('\n'))
.map((jsx, i) =>
`
function MDXSContent${i}({ components, ...props }) {
return (
${jsx.trim()}
)
}
`.trim()
)

const defaultExport = `
const MDXSWrapper = props => [
Copy link
Member Author

Choose a reason for hiding this comment

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

I guess we could actually make this the MDXLayout?

${jsxFragments.map((_, i) => ` <MDXSContent${i} {...props} />`).join(',\n')}
]

export default MDXWrapper
`.trim()

return [
importNodes.map(n => n.value.trim()).join('\n'),
'',
exportNodes
.filter(n => !n.default)
.map(n => n.value.trim())
.join('\n'),
'',
`const MDXSLayout = ${
layout
? layout.value
.replace(/^export\s+default\s+/, '')
Copy link
Member Author

Choose a reason for hiding this comment

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

We should prolly make this a shared util.

.replace(/;\s*$/, '')
: '"wrapper"'
}`,
'',
jsxFragments.join('\n\n'),
'',
defaultExport
].join('\n')
}
}
21 changes: 21 additions & 0 deletions packages/remark-mdxs/license
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2019 John Otander and Brent Jackson

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
54 changes: 54 additions & 0 deletions packages/remark-mdxs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"name": "remark-mdxs",
"version": "1.0.0-rc.0",
"description": "Support for multiple MDX documents in a single file",
"license": "MIT",
"keywords": [
"mdx",
"mdxs",
"markdown",
"react",
"jsx",
"remark",
"mdxast"
],
"homepage": "https://mdxjs.com",
"repository": "mdx-js/mdx",
"bugs": "https://github.com/mdx-js/mdx/issues",
"author": "John Otander <johnotander@gmail.com> (https://johno.com)",
"contributors": [
"John Otander <johnotander@gmail.com> (http://johnotander.com)",
"Brent Jackson <jacksonblack@gmail.com> (https://jxnblk.com)",
"Tim Neutkens <tim@zeit.co>",
"Matija Marohnić <matija.marohnic@gmail.com>",
"Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
],
"files": [
"index.js"
],
"dependencies": {
"@babel/core": "^7.2.2",
"@babel/helper-plugin-utils": "^7.0.0",
"@babel/plugin-proposal-object-rest-spread": "^7.3.2",
"@babel/plugin-syntax-jsx": "^7.2.0",
"is-alphabetical": "^1.0.2",
"remark-parse": "^6.0.0",
"unified": "^7.0.0",
"unist-util-remove": "^1.0.1",
"unist-util-visit": "^1.4.0"
},
"peerDependencies": {
"@mdx-js/mdx": "*"
},
"scripts": {
"test": "jest"
},
"jest": {
"testEnvironment": "node"
},
"devDependencies": {
"jest": "^24.0.0",
"remark-stringify": "^6.0.4",
"vfile": "^4.0.0"
}
}
73 changes: 73 additions & 0 deletions packages/remark-mdxs/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# @[mdx][]-js/mdxs

[![Build Status][build-badge]][build]
[![lerna][lerna-badge]][lerna]
[![Join the community on Spectrum][spectrum-badge]][spectrum]

> :warning: This project is currently in alpha

Multi-doc syntax support for MDX.

## Installation

```sh
npm install --save mdxs
```

## Usage

```js
const mdx = require('@mdx-js/mdx')
const mdxs = require('@mdx-js/mdxs')

const jsx = await mdx(FIXTURE, {
compilers: [mdxs]
})
```

## Syntax

```md

# Hello, I'm document 1

---

# Hello, I'm document 2
```

Copy link
Contributor

Choose a reason for hiding this comment

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

From a usage standpoint, is the thinking that you would use something like a separate webpack loader rule that only parses .mdxs files while handling .mdx files like any other?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah that's the idea 👍

Choose a reason for hiding this comment

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

That seems like it will turn out to be kind of unfortunate. It means we can't delineate between MDX content and MDXS content when fetching from remote sources that use text/markdown content types (mdx still doesn't have it's own entry in the mime-db and even if it did, we're going to want compat with markdown types from headless CMSs anyway).

Since every MDXs document is technically a valid MDX document, I'd much rather see a const export used to specify intent to be MDXs content.

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmm, that's a good point 🤔. I'd mostly been thinking about local fs. How do you think intent for MDXs should be expressed?

Something like?:

export const MDXS = true

Choose a reason for hiding this comment

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

yeah this is the approach I landed on as well. I think that the "first MDX file" in an MDXs document can be used as the "global exports" (compared to exports defined in subsequent "files"), so you'd end up with something like this where the first MDX doesn't have any content, but is just imports/exports.

// global export
export const MDXS = true
---
// file-local export
export const slideLayout = SomeComponent
# some slide
---
# another slide

## Contribute

See [`contributing.md` in `mdx-js/mdx`][contributing] for ways to get started.

This organisation has a [Code of Conduct][coc].
By interacting with this repository, organisation, or community you agree to
abide by its terms.

## License

[MIT][] © [John Otander][author]

<!-- Definitions -->

[build]: https://travis-ci.org/mdx-js/mdx

[build-badge]: https://travis-ci.org/mdx-js/mdx.svg?branch=master

[lerna]: https://lernajs.io/

[lerna-badge]: https://img.shields.io/badge/maintained%20with-lerna-cc00ff.svg

[spectrum]: https://spectrum.chat/mdx

[spectrum-badge]: https://withspectrum.github.io/badge/badge.svg

[contributing]: https://github.com/mdx-js/mdx/blob/master/contributing.md

[coc]: https://github.com/mdx-js/mdx/blob/master/code-of-conduct.md

[mit]: license

[mdx]: https://github.com/mdx-js/mdx

[author]: https://johno.com
41 changes: 41 additions & 0 deletions packages/remark-mdxs/test/__snapshots__/test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`correctly transpiles 1`] = `
"/* @jsx mdx */
import Foo from './bar'

export const author = 'fred'

const MDXSLayout = Foo

function MDXSContent0({ components, ...props }) {
return (
<h1>{\`Hello, world! \`}<Foo bar={{ baz: 'qux' }} /></h1>
)
}

function MDXSContent1({ components, ...props }) {
return (
<Baz>
Hi!
</Baz>
)
}

function MDXSContent2({ components, ...props }) {
return (
<h1>{\`I'm another document\`}</h1>


<p>{\`over here.\`}</p>
)
}

const MDXSWrapper = props => [
<MDXSContent0 {...props} />,
<MDXSContent1 {...props} />,
<MDXSContent2 {...props} />
]

export default MDXWrapper"
`;
30 changes: 30 additions & 0 deletions packages/remark-mdxs/test/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const mdx = require('../../mdx')
const remarkMdxs = require('..')

const FIXTURE = `
import Foo from './bar'
export const author = 'fred'
export default Foo

# Hello, world! <Foo bar={{ baz: 'qux' }} />

---

<Baz>
Hi!
</Baz>

---

# I'm another document

over here.
`

it('correctly transpiles', async () => {
const result = await mdx(FIXTURE, {
compilers: [remarkMdxs]
})

expect(result).toMatchSnapshot()
})