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

Trying to invoke mdx-bundler via a custom webpack loader in a Next.js app #177

Open
brianjenkins94 opened this issue Jun 5, 2022 · 8 comments

Comments

@brianjenkins94
Copy link

brianjenkins94 commented Jun 5, 2022

  • mdx-bundler version: v9.0.1
  • node version: v18.2.0
  • npm 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.

@brianjenkins94
Copy link
Author

brianjenkins94 commented Jun 6, 2022

Here is my minimal sample that reproduces the issue:

nextra-knockoff.zip


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
	});
}

@brianjenkins94
Copy link
Author

brianjenkins94 commented Jun 7, 2022

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, {})]
    });
  }
}

@brianjenkins94
Copy link
Author

brianjenkins94 commented Jun 7, 2022

So its got something to do with this I guess:

mdx-bundler/src/index.js

Lines 166 to 180 in 8a4e6a6

globalExternals({
...globals,
react: {
varName: 'React',
type: 'cjs',
},
'react-dom': {
varName: 'ReactDOM',
type: 'cjs',
},
'react/jsx-runtime': {
varName: '_jsx_runtime',
type: 'cjs',
},
}),

...but why?

@brianjenkins94
Copy link
Author

brianjenkins94 commented Jun 8, 2022

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.

@brianjenkins94
Copy link
Author

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?

@Markos-Th09
Copy link

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

@brianjenkins94
Copy link
Author

brianjenkins94 commented Jun 9, 2022

@Markos-Th09

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?

@Markos-Th09
Copy link

@Markos-Th09

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants