feat(fs.dev): Loading MDX is now ok
This commit is contained in:
parent
092c81f535
commit
c5e971e8a7
11 changed files with 231 additions and 49 deletions
10
packages/freesewing.dev/hooks/useMdx.js
Normal file
10
packages/freesewing.dev/hooks/useMdx.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
const useMdx = (slug=false) => {
|
||||||
|
if (!slug) null
|
||||||
|
const file = ['markdown', 'dev', ...slug.split('/'), 'en.md'].join('/')
|
||||||
|
const mdx = require(file)
|
||||||
|
return <p>{file}</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useMdx
|
|
@ -13,14 +13,15 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mdx-js/loader": "^2.0.0-rc.2",
|
"@mdx-js/loader": "^2.0.0-rc.2",
|
||||||
|
"@mdx-js/mdx": "^2.0.0-rc.2",
|
||||||
"@mdx-js/react": "^2.0.0-rc.2",
|
"@mdx-js/react": "^2.0.0-rc.2",
|
||||||
"@mdx-js/runtime": "next",
|
"@mdx-js/runtime": "next",
|
||||||
"daisyui": "^1.16.2",
|
"daisyui": "^1.16.2",
|
||||||
|
"lodash.get": "^4.4.2",
|
||||||
|
"lodash.orderby": "^4.6.0",
|
||||||
|
"lodash.set": "^4.3.2",
|
||||||
"next": "latest",
|
"next": "latest",
|
||||||
"react-swipeable": "^6.2.0",
|
"react-swipeable": "^6.2.0",
|
||||||
"lodash.orderby": "^4.6.0",
|
|
||||||
"lodash.get": "^4.4.2",
|
|
||||||
"lodash.set": "^4.3.2",
|
|
||||||
"remark-gfm": "^3.0.1",
|
"remark-gfm": "^3.0.1",
|
||||||
"remark-jargon": "^2.19.5"
|
"remark-jargon": "^2.19.5"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
import Page from 'shared/components/wrappers/page.js'
|
|
||||||
import useApp from 'site/hooks/useApp.js'
|
|
||||||
|
|
||||||
const MdxPage = props => {
|
|
||||||
const app = useApp()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Page app={app} title='This is not a homepage'>
|
|
||||||
<pre>{JSON.stringify(props, null, 2)}</pre>
|
|
||||||
</Page>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default MdxPage
|
|
||||||
|
|
||||||
export const getStaticProps = async () => {
|
|
||||||
|
|
||||||
return { props: { example: 'yes'} }
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getStaticPaths = async () => {
|
|
||||||
const paths = [
|
|
||||||
{
|
|
||||||
params: {
|
|
||||||
mdx: ['test'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
params: {
|
|
||||||
mdx: ['tost'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
return { paths, fallback: false }
|
|
||||||
}
|
|
||||||
|
|
114
packages/freesewing.dev/pages/[...mdxslug].js
Normal file
114
packages/freesewing.dev/pages/[...mdxslug].js
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
/*
|
||||||
|
* This page will be used to generate all markdown content
|
||||||
|
* which, on freesewing.dev, is pretty much the entire website
|
||||||
|
* apart from the home page.
|
||||||
|
*
|
||||||
|
* It uses a dynamic route and data fetching to do that.
|
||||||
|
* More info is available at the bottom of this page, or check
|
||||||
|
* https://nextjs.org/docs/basic-features/data-fetching
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Page wrapper - a MUST for all pages
|
||||||
|
*/
|
||||||
|
import Page from 'shared/components/wrappers/page.js'
|
||||||
|
|
||||||
|
/*
|
||||||
|
* useApp hook - also a MUST for all pages
|
||||||
|
*/
|
||||||
|
import useApp from 'site/hooks/useApp.js'
|
||||||
|
|
||||||
|
/*
|
||||||
|
* mdxMeta is generated in the prebuild step and contains
|
||||||
|
* meta-data about markdown content (titles, slugs, intro)
|
||||||
|
*/
|
||||||
|
import mdxMeta from 'site/prebuild/mdx.en.js'
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This loads MDX (markdown) from disk
|
||||||
|
*/
|
||||||
|
import mdxLoader from 'shared/mdx/loader'
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This wraps MDX making sure to include all components
|
||||||
|
* like Tip, Note, Example, and so on
|
||||||
|
*/
|
||||||
|
import MdxWrapper from 'shared/components/wrappers/mdx'
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The NextJS page object
|
||||||
|
*/
|
||||||
|
const MdxPage = props => {
|
||||||
|
|
||||||
|
// This hook is used for shared code and global state
|
||||||
|
const app = useApp()
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Each page should be wrapped in the Page wrapper component
|
||||||
|
* You MUST pass it the result of useApp() as the `app` prop
|
||||||
|
* and for MDX pages you can spread the props.page props to it
|
||||||
|
* to automatically set the title and navigation
|
||||||
|
*
|
||||||
|
* Like breadcrumbs and updating the primary navigation with
|
||||||
|
* active state
|
||||||
|
*/
|
||||||
|
return (
|
||||||
|
<Page app={app} {...props.page}>
|
||||||
|
<MdxWrapper mdx={props.mdx} />
|
||||||
|
</Page>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Default export is the NextJS page object
|
||||||
|
*/
|
||||||
|
export default MdxPage
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getStaticProps() is used to fetch data at build-time.
|
||||||
|
*
|
||||||
|
* On this page, it is loading the mdx (markdown) content
|
||||||
|
* from the markdown file on disk.
|
||||||
|
*
|
||||||
|
* This, in combination with getStaticPaths() below means this
|
||||||
|
* page will be used to render/generate all markdown/mdx content.
|
||||||
|
*
|
||||||
|
* To learn more, see: https://nextjs.org/docs/basic-features/data-fetching
|
||||||
|
*/
|
||||||
|
export async function getStaticProps({ params }) {
|
||||||
|
|
||||||
|
const mdx = await mdxLoader('en', 'dev', params.mdxslug.join('/'))
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
mdx,
|
||||||
|
page: {
|
||||||
|
slug: params.mdxslug.join('/'),
|
||||||
|
path: '/' + params.mdxslug.join('/'),
|
||||||
|
slugArray: params.mdxslug,
|
||||||
|
...mdxMeta[params.mdxslug.join('/')],
|
||||||
|
},
|
||||||
|
params
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getStaticPaths() is used to specify for which routes (think URLs)
|
||||||
|
* this page should be used to generate the result.
|
||||||
|
*
|
||||||
|
* On this page, it is returning a list of routes (think URLs) for all
|
||||||
|
* the mdx (markdown) content.
|
||||||
|
* That list comes from mdxMeta, which is build in the prebuild step
|
||||||
|
* and contains paths, titles, and intro for all markdown.
|
||||||
|
*
|
||||||
|
* To learn more, see: https://nextjs.org/docs/basic-features/data-fetching
|
||||||
|
*/
|
||||||
|
export async function getStaticPaths() {
|
||||||
|
return {
|
||||||
|
paths: Object.keys(mdxMeta).map(slug => '/'+slug),
|
||||||
|
fallback: false
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,8 +2,6 @@ import Page from 'shared/components/wrappers/page.js'
|
||||||
import useApp from 'site/hooks/useApp.js'
|
import useApp from 'site/hooks/useApp.js'
|
||||||
import ThemePicker from 'shared/components/theme-picker.js'
|
import ThemePicker from 'shared/components/theme-picker.js'
|
||||||
|
|
||||||
import nav from 'site/prebuild/navigation.js'
|
|
||||||
|
|
||||||
export default (props) => {
|
export default (props) => {
|
||||||
const app = useApp()
|
const app = useApp()
|
||||||
return (
|
return (
|
||||||
|
@ -15,6 +13,7 @@ export default (props) => {
|
||||||
>Toggle</button>
|
>Toggle</button>
|
||||||
</p>
|
</p>
|
||||||
<ThemePicker app={app} />
|
<ThemePicker app={app} />
|
||||||
|
|
||||||
</Page>
|
</Page>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
17
packages/freesewing.shared/components/elements/in-mdx.js
Normal file
17
packages/freesewing.shared/components/elements/in-mdx.js
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
// Generates a ReadMore block
|
||||||
|
export const ReadMore = props => (
|
||||||
|
<blockquote>
|
||||||
|
<h5>Read More</h5>
|
||||||
|
{props.children}
|
||||||
|
</blockquote>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const Note = props => (
|
||||||
|
<blockquote classname={` `}>
|
||||||
|
<h5>Note</h5>
|
||||||
|
{props.children}
|
||||||
|
</blockquote>
|
||||||
|
)
|
||||||
|
export const Tip = () => <p>Tip</p>
|
||||||
|
export const Example = () => <p>Example</p>
|
||||||
|
|
|
@ -47,7 +47,7 @@ const DefaultLayout = props => {
|
||||||
`}>
|
`}>
|
||||||
<PrimaryMenu app={props.app}/>
|
<PrimaryMenu app={props.app}/>
|
||||||
</aside>
|
</aside>
|
||||||
<section>
|
<section className='max-w-screen-lg'>
|
||||||
<H1>{props.title}</H1>
|
<H1>{props.title}</H1>
|
||||||
{props.children}
|
{props.children}
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -12,10 +12,13 @@ const keepClosed = ['blog', 'showcase', ]
|
||||||
|
|
||||||
// TODO: For now we force tailwind to pickup these styles
|
// TODO: For now we force tailwind to pickup these styles
|
||||||
// At some point this should 'just work' though, but let's not worry about it now
|
// At some point this should 'just work' though, but let's not worry about it now
|
||||||
const force = <p className="w-6 mr-3"/>
|
const force = [
|
||||||
|
<p className="w-6 mr-2"/>,
|
||||||
|
<p className="w-8 mr-3"/>
|
||||||
|
]
|
||||||
|
|
||||||
// Component for the collapse toggle
|
// Component for the collapse toggle
|
||||||
const Chevron = ({w=8, m=1}) => <svg
|
const Chevron = ({w=8, m=2}) => <svg
|
||||||
className={`fill-current opacity-75 w-${w} h-${w} mr-${m} details-toggle`}
|
className={`fill-current opacity-75 w-${w} h-${w} mr-${m} details-toggle`}
|
||||||
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
|
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
|
||||||
<path d="M12.95 10.707l.707-.707L8 4.343 6.586 5.757 10.828 10l-4.242 4.243L8 15.657l4.95-4.95z"/>
|
<path d="M12.95 10.707l.707-.707L8 4.343 6.586 5.757 10.828 10l-4.242 4.243L8 15.657l4.95-4.95z"/>
|
||||||
|
@ -30,8 +33,8 @@ const SubLevel = ({ nodes={} }) => (
|
||||||
<ul className='list-inside'>
|
<ul className='list-inside'>
|
||||||
{currentChildren(nodes).map(child => (Object.keys(child).length > 4)
|
{currentChildren(nodes).map(child => (Object.keys(child).length > 4)
|
||||||
? (
|
? (
|
||||||
<li>
|
<li key={child.__slug}>
|
||||||
<details key={child.__slug}>
|
<details>
|
||||||
<summary className={`
|
<summary className={`
|
||||||
flex flex-row gap-4 font-bold
|
flex flex-row gap-4 font-bold
|
||||||
hover:cursor-row-resize
|
hover:cursor-row-resize
|
||||||
|
@ -113,7 +116,6 @@ const TopLevel = ({ icon, title, nav, current, slug, showChildren=false }) => (
|
||||||
// TODO: Get rid of this when markdown has been restructured
|
// TODO: Get rid of this when markdown has been restructured
|
||||||
const remove = ['contributors', 'developers', 'editors', 'translators']
|
const remove = ['contributors', 'developers', 'editors', 'translators']
|
||||||
const Navigation = ({ nav, app }) => {
|
const Navigation = ({ nav, app }) => {
|
||||||
console.log(nav.en)
|
|
||||||
const output = []
|
const output = []
|
||||||
for (const key of Object.keys(nav[app.language]).sort()) {
|
for (const key of Object.keys(nav[app.language]).sort()) {
|
||||||
if (hide.indexOf(key) === -1) output.push(<TopLevel
|
if (hide.indexOf(key) === -1) output.push(<TopLevel
|
||||||
|
|
42
packages/freesewing.shared/components/wrappers/mdx.js
Normal file
42
packages/freesewing.shared/components/wrappers/mdx.js
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* This is used to wrap MDX as returned from the mdxLoader
|
||||||
|
* method (see shared/mdx/loader.js)
|
||||||
|
* It is NOT for wrapping plain markdown/mdx
|
||||||
|
*/
|
||||||
|
import { useState, useEffect, Fragment } from 'react'
|
||||||
|
|
||||||
|
// See: https://mdxjs.com/guides/mdx-on-demand/
|
||||||
|
import { run } from '@mdx-js/mdx'
|
||||||
|
import * as runtime from 'react/jsx-runtime.js'
|
||||||
|
|
||||||
|
// Components that are available in all MDX
|
||||||
|
import * as dfltComponents from 'shared/components/elements/in-mdx'
|
||||||
|
|
||||||
|
const MdxWrapper = ({mdx, components={}}) => {
|
||||||
|
|
||||||
|
const [mdxModule, setMdxModule] = useState()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
;(async () => {
|
||||||
|
setMdxModule(await run(mdx, runtime))
|
||||||
|
})()
|
||||||
|
}, [mdx])
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We use some default components that are available
|
||||||
|
* everywhere in MDX, but we also accept passing in
|
||||||
|
* extra components via props
|
||||||
|
*/
|
||||||
|
const allComponents = {
|
||||||
|
...dfltComponents,
|
||||||
|
...components
|
||||||
|
}
|
||||||
|
|
||||||
|
// React component for MDX content
|
||||||
|
const MdxContent = mdxModule ? mdxModule.default : Fragment
|
||||||
|
|
||||||
|
return <MdxContent components={allComponents}/>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MdxWrapper
|
||||||
|
|
|
@ -8,7 +8,7 @@ const config = site => ({
|
||||||
externalDir: true,
|
externalDir: true,
|
||||||
esmExternals: true,
|
esmExternals: true,
|
||||||
},
|
},
|
||||||
pageExtensions: [ 'js' ],
|
pageExtensions: [ 'js', 'md' ],
|
||||||
webpack: (config, options) => {
|
webpack: (config, options) => {
|
||||||
|
|
||||||
// Fixes npm packages that depend on node modules
|
// Fixes npm packages that depend on node modules
|
||||||
|
@ -48,6 +48,7 @@ const config = site => ({
|
||||||
// Aliases
|
// Aliases
|
||||||
config.resolve.alias.shared = path.resolve('../freesewing.shared/')
|
config.resolve.alias.shared = path.resolve('../freesewing.shared/')
|
||||||
config.resolve.alias.site = path.resolve(`../freesewing.${site}/`)
|
config.resolve.alias.site = path.resolve(`../freesewing.${site}/`)
|
||||||
|
config.resolve.alias.markdown = path.resolve(`../../markdown/${site}/`)
|
||||||
|
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
33
packages/freesewing.shared/mdx/loader.js
Normal file
33
packages/freesewing.shared/mdx/loader.js
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// We need fs and path to read from disk
|
||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
// MDX compiler
|
||||||
|
import { compile } from '@mdx-js/mdx'
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Summary: Loads markdown from disk and compiles it as MDX.
|
||||||
|
*
|
||||||
|
* @param (string) language - language to load (eg: 'en')
|
||||||
|
* @param (string) site - site to load (either 'dev' or 'org')
|
||||||
|
* @param (string) slug - slug of the page (eg: 'guides/patterns')
|
||||||
|
*
|
||||||
|
* @link https://mdxjs.com/guides/mdx-on-demand/
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const mdxLoader = async (language, site, slug) => {
|
||||||
|
|
||||||
|
// TODO: Will this work on Windows?
|
||||||
|
const md = await fs.promises.readFile(
|
||||||
|
path.resolve(`../../markdown/${site}/${slug}/${language}.md`),
|
||||||
|
'utf-8'
|
||||||
|
)
|
||||||
|
|
||||||
|
const mdx = String(
|
||||||
|
await compile(md, { outputFormat: 'function-body' })
|
||||||
|
)
|
||||||
|
|
||||||
|
return mdx
|
||||||
|
}
|
||||||
|
|
||||||
|
export default mdxLoader
|
Loading…
Add table
Add a link
Reference in a new issue