Trying to invoke `mdx-bundler` via a custom webpack loader in a Next.js app
brianjenkins94 opened this issue · comments
mdx-bundler
version: v9.0.1node
version: v18.2.0npm
version: v8.9.0
Relevant code or config:
// next.config.mjs
export default {
"pageExtensions": ["js", "jsx", "ts", "tsx", "md", "mdx"],
"webpack": function(config, options) {
config.module.rules.push({
"test": /\.mdx?$/,
"use": [
options.defaultLoaders.babel,
{
"loader": "./util/webpack/loader.cjs",
"options": { "theme": "./layouts/docs" }
}
]
});
return config;
}
};
// util/webpack/loader.cjs
const path = require("path");
const { bundleMDX } = require("mdx-bundler");
const basePath = path.join(__dirname, "..", "..");
module.exports = async function(source) {
const callback = this.async();
this.addContextDependency(path.join(basePath, "pages"));
const { code, frontmatter } = await bundleMDX({
"source": source,
});
callback(null, `
import { getMDXComponent } from "mdx-bundler";
export default function(props) {
return getMDXComponent("${code}");
}
`);
};
What you did:
Attempted to render a MDX file via a custom webpack loader.
What happened:
> Ready on http://localhost:3000
wait - compiling /path/to/mdx/file (client and server)...
wait - compiling...
error - ./pages/path/to/mdx/file/index.mdx
Error:
x Expected ',', got 'object'
,----
5 | return getMDXComponent("var Component=(()=>{var d=Object.create;var c=Object.defineProperty;var h=Object.getOwnPropertyDescriptor;var u=Object.getOwnPropertyNames;var m=Object.getPrototypeOf,p=Object.prototype.hasOwnProperty;var g=(r,i)=>()=>(i||r((i={exports:{}}).exports,i),i.exports),S=(r,i)=>{for(var o in i)c(r,o,{get:i[o],enumerable:!0})},s=(r,i,o,n)=>{if(i&&typeof i=="object"||typeof i=="function")for(let t of u(i))!p.call(r,t)&&t!==o&&c(r,t,{get:()=>i[t],enumerable:!(n=h(i,t))||n.enumerable});return r};var y=(r,i,o)=>(o=r!=null?d(m(r)):{},s(i||!r||!r.__esModule?c(o,"default",{value:r,enumerable:!0}):o,r)),P=r=>s(c({},"__esModule",{value:!0}),r);var a=g((z,l)=>{l.exports=_jsx_runtime});var D={};S(D,{default:()=>v});var e=y(a());function f(r={}){let{wrapper:i}=r.components||{};return i?(0,e.jsx)(i,Object.assign({},r,{children:(0,e.jsx)(o,{})})):o();function o(){let n=Object.assign({h1:"h1",p:"p",h2:"h2",img:"img",ul:"ul",li:"li",strong:"strong",em:"em",h3:"h3"},r.components);return(0,e.jsxs)(e.Fragment,{children:[(0,e.jsx)(n.h1,{children:"Introduction"}),`
: ^^^^^^
`----
Caused by:
0: failed to process input file
1: Syntax Error
Reproduction repository:
WIP
Problem description:
I had a clever idea to copy what Nextra is doing to leverage Next.js's file-system based routing so all my MDX files would be rendered automatically, but I'm clearly not invoking mdx-bundler
correctly. I'll work on a minimal sample to reproduce the issue and see what I can figure out.
Here is my minimal sample that reproduces the issue:
And here's an example of what Nextra returns:
import withLayout from "./layouts/docs";
import { withSSG } from "nextra/ssg";
/*@jsxRuntime automatic @jsxImportSource react*/
import { useMDXComponents as _provideComponents } from "@mdx-js/react";
function MDXContent(props = {}) {
const { "wrapper": MDXLayout } = { ..._provideComponents(), ...props.components };
return MDXLayout ? <MDXLayout {...props}><_createMdxContent /></MDXLayout> : _createMdxContent();
function _createMdxContent() {
const _components = {
"p": "p",
"img": "img",
"h1": "h1",
"strong": "strong",
"h2": "h2",
"h3": "h3",
"ul": "ul",
"li": "li",
..._provideComponents(),
...props.components
};
return <>
<_components.p>
<_components.img src="path/to/image.png" alt="alt text" />
</_components.p>
{"\n"}
<_components.h1>{"Introduction"}</_components.h1>
{"\n"}
<_components.p>{"Content "}<_components.strong>{"strong content"}</_components.strong>{" and more content."}</_components.p>
</>;
}
}
const _mdxContent = <MDXContent />;
export default function NextraPage(props) {
return withSSG(withLayout({
"filename": "C:/path/to/page.mdx",
"route": "/path/to/page",
"meta": {},
"pageMap": [/* Giant object with the keys: `name`, `children`, `route` */]
}, null))({
...props,
"children": _mdxContent
});
}
No dice for:
// util/webpack/loader.cjs
const path = require("path");
const { bundleMDX } = require("mdx-bundler");
const { getMDXComponent } = require("mdx-bundler/client");
const basePath = path.join(__dirname, "..", "..");
module.exports = async function(source) {
const callback = this.async();
this.addContextDependency(path.join(basePath, "pages"));
let { code, frontmatter } = await bundleMDX({
"source": source,
"esbuildOptions": function(options, frontmatter) {
options.minify = false;
return options;
},
});
const Component = getMDXComponent(code)
callback(null, Component.toString());
};
either. Yields:
error - Error: The default export is not a React Component in page: "/"
And:
callback(null, "export default " + Component.toString());
Yields:
ReferenceError: import_jsx_runtime is not defined
10 | strong: "strong"
11 | }, props.components);
> 12 | return (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, {
| ^
13 | children: [(0, import_jsx_runtime.jsx)(_components.h1, {
14 | children: "Wahoo"
15 | }), "\n", "\n", (0, import_jsx_runtime.jsxs)(_components.p, {
Where the Component
is:
Component
function MDXContent(props = {}) {
const { wrapper: MDXLayout } = props.components || {};
return MDXLayout ? (0, import_jsx_runtime.jsx)(MDXLayout, Object.assign({}, props, {
children: (0, import_jsx_runtime.jsx)(_createMdxContent, {})
})) : _createMdxContent();
function _createMdxContent() {
const _components = Object.assign({
h1: "h1",
p: "p",
strong: "strong"
}, props.components);
return (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, {
children: [(0, import_jsx_runtime.jsx)(_components.h1, {
children: "Wahoo"
}), "\n", "\n", (0, import_jsx_runtime.jsxs)(_components.p, {
children: ["Here's a ", (0, import_jsx_runtime.jsx)(_components.strong, {
children: "neat"
}), " demo:"]
}), "\n", (0, import_jsx_runtime.jsx)(demo_default, {})]
});
}
}
I was pretty sure I tried this, but this:
const path = require("path");
const { bundleMDX } = require("mdx-bundler");
const { getMDXComponent } = require("mdx-bundler/client");
const basePath = path.join(__dirname, "..", "..");
module.exports = async function(source) {
const callback = this.async();
this.addContextDependency(path.join(basePath, "pages"));
let { code, frontmatter } = await bundleMDX({
"source": source
});
const Component = getMDXComponent(code)
callback(null, "import * as import_jsx_runtime from \"react/jsx-runtime\"; export default " + Component.toString());
};
seems to have done it.
I just can't tell if this is needlessly bundling react/jsx-runtime
when it could be available in some other way.
Outstanding questions:
- Is the above solution needlessly bundling
react/jsx-runtime
? - Does Next.js somehow expose
react/jsx-runtime
in whatever it ships to the client? - Or does Next.js have some way of compiling what it's given to native
document.createElement
calls during the build process?
The jsx runtime is used in next.js as of the "new"(has been around since next.js 9.5) react transform and it is bundled anyways so you aren't bundling it for no reason
Not sure I follow. Are you saying I should have to import jsx-runtime
and that what I’m doing isn’t adding redundant dependencies to the bundle?
Not sure I follow. Are you saying I should have to import
jsx-runtime
and that what I’m doing isn’t adding redundant dependencies to the bundle?
What I am saying is that importing the jsx runtime is fine and you aren't adding any extra dependencies