From c5e971e8a7f1122120888f5d9a058ab2c1698256 Mon Sep 17 00:00:00 2001 From: Joost De Cock Date: Sat, 18 Dec 2021 09:54:53 +0100 Subject: [PATCH] feat(fs.dev): Loading MDX is now ok --- packages/freesewing.dev/hooks/useMdx.js | 10 ++ packages/freesewing.dev/package.json | 7 +- packages/freesewing.dev/pages/[...mdx].js | 37 ------ packages/freesewing.dev/pages/[...mdxslug].js | 114 ++++++++++++++++++ packages/freesewing.dev/pages/index.js | 3 +- .../components/elements/in-mdx.js | 17 +++ .../components/layouts/default.js | 2 +- .../components/navigation/primary.js | 12 +- .../components/wrappers/mdx.js | 42 +++++++ packages/freesewing.shared/config/next.mjs | 3 +- packages/freesewing.shared/mdx/loader.js | 33 +++++ 11 files changed, 231 insertions(+), 49 deletions(-) create mode 100644 packages/freesewing.dev/hooks/useMdx.js delete mode 100644 packages/freesewing.dev/pages/[...mdx].js create mode 100644 packages/freesewing.dev/pages/[...mdxslug].js create mode 100644 packages/freesewing.shared/components/elements/in-mdx.js create mode 100644 packages/freesewing.shared/components/wrappers/mdx.js create mode 100644 packages/freesewing.shared/mdx/loader.js diff --git a/packages/freesewing.dev/hooks/useMdx.js b/packages/freesewing.dev/hooks/useMdx.js new file mode 100644 index 00000000000..a10f3d8970b --- /dev/null +++ b/packages/freesewing.dev/hooks/useMdx.js @@ -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

{file}

+} + +export default useMdx diff --git a/packages/freesewing.dev/package.json b/packages/freesewing.dev/package.json index 0f71e468b3a..fccb37d52e4 100644 --- a/packages/freesewing.dev/package.json +++ b/packages/freesewing.dev/package.json @@ -13,14 +13,15 @@ }, "dependencies": { "@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/runtime": "next", "daisyui": "^1.16.2", + "lodash.get": "^4.4.2", + "lodash.orderby": "^4.6.0", + "lodash.set": "^4.3.2", "next": "latest", "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-jargon": "^2.19.5" }, diff --git a/packages/freesewing.dev/pages/[...mdx].js b/packages/freesewing.dev/pages/[...mdx].js deleted file mode 100644 index 258c297bf0a..00000000000 --- a/packages/freesewing.dev/pages/[...mdx].js +++ /dev/null @@ -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 ( - -
{JSON.stringify(props, null, 2)}
-
- ) -} - -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 } -} - diff --git a/packages/freesewing.dev/pages/[...mdxslug].js b/packages/freesewing.dev/pages/[...mdxslug].js new file mode 100644 index 00000000000..d0c2eb710bf --- /dev/null +++ b/packages/freesewing.dev/pages/[...mdxslug].js @@ -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 ( + + + + ) +} + +/* + * 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 + } +} diff --git a/packages/freesewing.dev/pages/index.js b/packages/freesewing.dev/pages/index.js index 3e66a8ae54f..d7403a778e7 100644 --- a/packages/freesewing.dev/pages/index.js +++ b/packages/freesewing.dev/pages/index.js @@ -2,8 +2,6 @@ import Page from 'shared/components/wrappers/page.js' import useApp from 'site/hooks/useApp.js' import ThemePicker from 'shared/components/theme-picker.js' -import nav from 'site/prebuild/navigation.js' - export default (props) => { const app = useApp() return ( @@ -15,6 +13,7 @@ export default (props) => { >Toggle

+ ) } diff --git a/packages/freesewing.shared/components/elements/in-mdx.js b/packages/freesewing.shared/components/elements/in-mdx.js new file mode 100644 index 00000000000..3efeeeb48ba --- /dev/null +++ b/packages/freesewing.shared/components/elements/in-mdx.js @@ -0,0 +1,17 @@ +// Generates a ReadMore block +export const ReadMore = props => ( +
+
Read More
+ {props.children} +
+) + +export const Note = props => ( +
+
Note
+ {props.children} +
+) +export const Tip = () =>

Tip

+export const Example = () =>

Example

+ diff --git a/packages/freesewing.shared/components/layouts/default.js b/packages/freesewing.shared/components/layouts/default.js index dbf08b54b6b..abeeff1251b 100644 --- a/packages/freesewing.shared/components/layouts/default.js +++ b/packages/freesewing.shared/components/layouts/default.js @@ -47,7 +47,7 @@ const DefaultLayout = props => { `}> -
+

{props.title}

{props.children}
diff --git a/packages/freesewing.shared/components/navigation/primary.js b/packages/freesewing.shared/components/navigation/primary.js index f1aaf4d735d..77c7163b545 100644 --- a/packages/freesewing.shared/components/navigation/primary.js +++ b/packages/freesewing.shared/components/navigation/primary.js @@ -12,10 +12,13 @@ const keepClosed = ['blog', 'showcase', ] // 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 -const force =

+const force = [ +

, +

+] // Component for the collapse toggle -const Chevron = ({w=8, m=1}) => @@ -30,8 +33,8 @@ const SubLevel = ({ nodes={} }) => (

    {currentChildren(nodes).map(child => (Object.keys(child).length > 4) ? ( -
  • -
    +
  • +
    ( // TODO: Get rid of this when markdown has been restructured const remove = ['contributors', 'developers', 'editors', 'translators'] const Navigation = ({ nav, app }) => { - console.log(nav.en) const output = [] for (const key of Object.keys(nav[app.language]).sort()) { if (hide.indexOf(key) === -1) output.push( { + + 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 +} + +export default MdxWrapper + diff --git a/packages/freesewing.shared/config/next.mjs b/packages/freesewing.shared/config/next.mjs index 7a469c6b689..77778ae5aa6 100644 --- a/packages/freesewing.shared/config/next.mjs +++ b/packages/freesewing.shared/config/next.mjs @@ -8,7 +8,7 @@ const config = site => ({ externalDir: true, esmExternals: true, }, - pageExtensions: [ 'js' ], + pageExtensions: [ 'js', 'md' ], webpack: (config, options) => { // Fixes npm packages that depend on node modules @@ -48,6 +48,7 @@ const config = site => ({ // Aliases config.resolve.alias.shared = path.resolve('../freesewing.shared/') config.resolve.alias.site = path.resolve(`../freesewing.${site}/`) + config.resolve.alias.markdown = path.resolve(`../../markdown/${site}/`) return config } diff --git a/packages/freesewing.shared/mdx/loader.js b/packages/freesewing.shared/mdx/loader.js new file mode 100644 index 00000000000..4bb51ab3129 --- /dev/null +++ b/packages/freesewing.shared/mdx/loader.js @@ -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