This commit is contained in:
sup39 2022-11-04 05:00:12 +09:00
commit e3f0b7dbf5
9 changed files with 1487 additions and 0 deletions

9
.eslintrc.js Normal file
View file

@ -0,0 +1,9 @@
module.exports = {
env: {
node: true,
es2020: true,
},
extends: [
'@sup39/basic',
],
};

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
node_modules/

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 sup39
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.

117
README.md Normal file
View file

@ -0,0 +1,117 @@
# rehype-mdx-component-wrapper
A rehype plugin to wrap the mdx component with external component
## Installation
```bash
# If you are using yarn
yarn add @sup39/rehype-mdx-component-wrapper
# If you are using npm
npm install @sup39/rehype-mdx-component-wrapper
```
## Configuration
|property|type|description|
|--|--|--|
|`name`|`string`|Name of the wrapper component. Default value is `'MDXRoot'`|
|`path`|`string`|Path to the wrapper component. Default value is `'@/MDXRoot'`|
|`props`|`string[]`|Local variable names to be passed to the wrapper component. Default value is `[]`|
## Example Usage of Next.js
This plugin can be used to pass the metadata (frontmatter) parsed by
[remark-mdx-frontmatter](https://github.com/remcohaszing/remark-mdx-frontmatter)
to the wrapper component.
For instance, you can use the metadata to create footer with author name for all mdx
without explicitly writing JSX in the files.
### Settings next.config.mjs
In next.config.mjs:
```javascript
/*
You may need to install remark-mdx-frontmatter
$ yarn add remark-frontmatter remark-mdx-frontmatter
*/
import mdx from '@next/mdx';
import remarkFrontmatter from 'remark-frontmatter';
import remarkMdxFrontmatter from 'remark-mdx-frontmatter';
import ComponentWrapper from '@sup39/rehype-mdx-component-wrapper';
const withMDX = mdx({
extension: /\.mdx?$/,
options: {
remarkPlugins: [
remarkFrontmatter,
// export all metadata into an object with name `meta`
() => remarkMdxFrontmatter({name: 'meta'}),
],
rehypePlugins: [() => ComponentWrapper({
name: 'MDXRoot',
path: '@/MDXRoot',
// pass the exported `meta` object to the wrapper component
props: ['meta'],
})],
},
});
export default withMDX({
pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'],
});
```
### Setup Alias
In your tsconfig, add `baseUrl` and `paths` like the following to create alias.
```json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["components/*"],
// ...
}
// ...
}
// ...
}
```
### Define a Wrapper Component
Define a wrapper component in `components/`. All `props` passed to the MDX file and the local exported variables with name in `props` are passed to the component as props.
For example, create file `components/MDXRoot.tsx` with the following contents:
```tsx
import Head from 'next/head'
export default function MDXRoot({meta, children}: any) {
return <>
<Head>
<title>{meta.title}</title>
<meta name="description" content={meta.description} />
</Head>
{children}
<footer>
<div>Author: {meta.author}</div>
</footer>
</>;
}
```
### Write mdx
```mdx
---
title: YAML is enough to define metadata!
description: You can add other meta tags too :)
author: sup39
---
## No need to write JSX in all mdx files!
```
### Result
The final output will roughly be:
```html
<head>
<title>YAML is enough to define metadata!</title>
<meta name="description" content="You can add other meta tags too :)">
</head>
<h2>No need to write JSX in all mdx files!</h2>
<footer><div>Author: sup39</div></footer>
```

11
index.d.ts vendored Normal file
View file

@ -0,0 +1,11 @@
export = ComponentWrapper;
/** @type {import('unified').Plugin<[ComponentWrapperOptions?], import('hast').Root>} */
declare const ComponentWrapper: import('unified').Plugin<[ComponentWrapperOptions?], import('hast').Root>;
declare namespace ComponentWrapper {
export { ComponentWrapperOptions };
}
type ComponentWrapperOptions = {
name?: string;
path?: string;
props?: string[];
};

90
index.js Normal file
View file

@ -0,0 +1,90 @@
/**
* @typedef {{
* name?: string
* path?: string
* props?: string[]
* }} ComponentWrapperOptions
*/
/** @param {any} expression */
const makeExpression = expression => ({
estree: {
type: 'Program',
sourceType: 'module',
body: [{
type: 'ExpressionStatement',
expression,
}],
},
});
/**
* @param {string} propName
* @param {any} expression
*/
const makeJsxAttr = (propName, expression) => ({
type: 'mdxJsxAttribute',
name: propName,
value: {
type: 'mdxJsxAttributeValueExpression',
data: makeExpression(expression),
},
});
/** @type {import('unified').Plugin<[ComponentWrapperOptions?], import('hast').Root>} */
const ComponentWrapper = ({
name = 'MDXRoot',
path = '@/MDXRoot',
props = [],
} = {}) => root => {
const {children} = root;
root.children = [
// import MDXRoot from '@/MDXRoot'
/**@type{import('mdast-util-mdx').MdxjsEsm}*/({
type: 'mdxjsEsm',
data: {
estree: {
type: 'Program',
sourceType: 'module',
body: [{
type: 'ImportDeclaration',
source: {type: 'Literal', value: path},
specifiers: [{
type: 'ImportDefaultSpecifier',
local: {type: 'Identifier', name: name},
}],
}],
},
},
}),
// <MDXRoot {...props}>{children}</MDXRoot>
/**@type{import('mdast-util-mdx').MdxJsxFlowElement}*/({
type: 'mdxJsxFlowElement',
name,
children,
attributes: [
// {...props}
{
type: 'mdxJsxExpressionAttribute',
value: '{...props}',
data: makeExpression({
type: 'ObjectExpression',
properties: [{
type: 'SpreadElement',
argument: {
type: 'Identifier',
name: 'props',
},
}],
}),
},
// extra local props
...props.map(prop => makeJsxAttr(prop, {
type: 'Identifier',
name: prop,
})),
],
}),
];
};
module.exports = ComponentWrapper;

26
package.json Normal file
View file

@ -0,0 +1,26 @@
{
"name": "@sup39/rehype-mdx-component-wrapper",
"version": "0.1.0",
"author": "sup39",
"repository": "https://github.com/sup39/rehype-mdx-component-wrapper",
"description": "A rehype plugin to wrap the mdx component with external component",
"license": "MIT",
"main": "index.js",
"types": "index.d.ts",
"files": [
"index.js",
"index.d.ts"
],
"scripts": {
"build": "tsc",
"lint": "eslint index.js"
},
"devDependencies": {
"@sup39/eslint-config-basic": "^0.1.5",
"@types/hast": "^2.3.4",
"eslint": "^8.26.0",
"mdast-util-mdx": "^2.0.0",
"typescript": "^4.8.4",
"unified": "^10.1.2"
}
}

11
tsconfig.json Normal file
View file

@ -0,0 +1,11 @@
{
"include": ["index.js"],
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"lib": ["es2020"],
"strict": true,
"declaration": true,
"emitDeclarationOnly": true
}
}

1201
yarn.lock Normal file

File diff suppressed because it is too large Load diff