diff --git a/README.md b/README.md index f5a30cd..cb9d9a6 100644 --- a/README.md +++ b/README.md @@ -290,13 +290,13 @@ database. If your MDX doesn't reference other files (or only imports things from #### xdmOptions -This allows you to modify the built-in xdm configuration (passed to -xdm.compile). This can be helpful for specifying your own +This allows you to modify the built-in xdm configuration (passed to the xdm +esbuild plugin). This can be helpful for specifying your own remarkPlugins/rehypePlugins. ```ts bundleMDX(mdxString, { - xdmOptions(input, options) { + xdmOptions(options) { // this is the recommended way to add custom remark/rehype plugins: // The syntax might look weird, but it protects you in case we add/remove // plugins in the future. diff --git a/package.json b/package.json index f72afbd..f42784c 100644 --- a/package.json +++ b/package.json @@ -43,22 +43,23 @@ "@babel/runtime": "^7.14.0", "@esbuild-plugins/node-resolve": "^0.1.4", "@fal-works/esbuild-plugin-global-externals": "^2.1.1", - "esbuild": "^0.11.16", + "esbuild": "^0.11.20", "gray-matter": "^4.0.3", "jsdom": "^16.5.3", "remark-frontmatter": "^3.0.0", "remark-mdx-frontmatter": "^1.0.1", "uvu": "^0.5.1", - "xdm": "^1.9.0" + "xdm": "^1.11.0" }, "devDependencies": { "@testing-library/react": "^11.2.6", "@types/jsdom": "^16.2.10", - "@types/react": "^17.0.4", + "@types/react": "^17.0.5", "@types/react-dom": "^17.0.3", "cross-env": "^7.0.3", "kcd-scripts": "^10.0.0", "left-pad": "^1.3.0", + "mdx-test-data": "^1.0.1", "react": "^17.0.2", "react-dom": "^17.0.2", "remark-mdx-images": "^1.0.2", diff --git a/src/__tests__/index.js b/src/__tests__/index.js index dba87a9..ea18655 100644 --- a/src/__tests__/index.js +++ b/src/__tests__/index.js @@ -149,7 +149,7 @@ import Demo from './demo' assert.equal( error.message, `Build failed with 1 error: -__mdx_bundler_fake_dir__/_mdx_bundler_entry_point.mdx:2:17: error: Could not resolve "./demo"`, +__mdx_bundler_fake_dir__/_mdx_bundler_entry_point.mdx:3:17: error: Could not resolve "./demo"`, ) }) @@ -189,7 +189,7 @@ import Demo from './demo.blah' assert.equal( error.message, `Build failed with 1 error: -__mdx_bundler_fake_dir__/_mdx_bundler_entry_point.mdx:2:17: error: [plugin: JavaScript plugins] Invalid loader: "blah" (valid: js, jsx, ts, tsx, css, json, text, base64, dataurl, file, binary)`, +__mdx_bundler_fake_dir__/_mdx_bundler_entry_point.mdx:3:17: error: [plugin: JavaScript plugins] Invalid loader: "blah" (valid: js, jsx, ts, tsx, css, json, text, base64, dataurl, file, binary)`, ) }) @@ -303,7 +303,7 @@ import {Sample} from './other/sample-component' const {code} = await bundleMDX(mdxSource, { cwd: process.cwd(), - xdmOptions: (vFile, options) => { + xdmOptions: options => { options.remarkPlugins = [remarkMdxImages] return options @@ -341,7 +341,7 @@ test('should output assets', async () => { const {code} = await bundleMDX(mdxSource, { cwd: process.cwd(), - xdmOptions: (vFile, options) => { + xdmOptions: options => { options.remarkPlugins = [remarkMdxImages] return options @@ -367,7 +367,7 @@ test('should output assets', async () => { const error = /** @type Error */ (await bundleMDX(mdxSource, { cwd: process.cwd(), - xdmOptions: (vFile, options) => { + xdmOptions: options => { options.remarkPlugins = [remarkMdxImages] return options @@ -390,4 +390,25 @@ test('should output assets', async () => { ) }) +test('should support mdx from node_modules', async () => { + const mdxSource = ` +import MdxData from 'mdx-test-data' + +Local Content + + + `.trim() + + const {code} = await bundleMDX(mdxSource, {}) + + const Component = getMDXComponent(code) + + const {container} = render(React.createElement(Component)) + + assert.match( + container.innerHTML, + 'Mdx file published as an npm package, for testing purposes.', + ) +}) + test.run() diff --git a/src/index.js b/src/index.js index 28ef0e5..04d8ae3 100644 --- a/src/index.js +++ b/src/index.js @@ -21,7 +21,7 @@ async function bundleMDX( mdxSource, { files = {}, - xdmOptions = (vfileCompatible, options) => options, + xdmOptions = options => options, esbuildOptions = options => options, globals = {}, cwd = path.join(process.cwd(), `__mdx_bundler_fake_dir__`), @@ -35,8 +35,7 @@ async function bundleMDX( // xdm is a native ESM, and we're running in a CJS context. This is the // only way to import ESM within CJS - const [{compile: compileMDX}, {default: xdmESBuild}] = await Promise.all([ - await import('xdm'), + const [{default: xdmESBuild}] = await Promise.all([ await import('xdm/esbuild.js'), ]) // extract the frontmatter @@ -56,18 +55,33 @@ async function bundleMDX( name: 'inMemory', setup(build) { build.onResolve({filter: /.*/}, ({path: filePath, importer}) => { - if (filePath === entryPath) - return {path: filePath, pluginData: {inMemory: true}} + if (filePath === entryPath) { + return { + path: filePath, + pluginData: {inMemory: true, contents: absoluteFiles[filePath]}, + } + } const modulePath = path.resolve(path.dirname(importer), filePath) - if (modulePath in absoluteFiles) - return {path: modulePath, pluginData: {inMemory: true}} + if (modulePath in absoluteFiles) { + return { + path: modulePath, + pluginData: {inMemory: true, contents: absoluteFiles[modulePath]}, + } + } for (const ext of ['.js', '.ts', '.jsx', '.tsx', '.json', '.mdx']) { const fullModulePath = `${modulePath}${ext}` - if (fullModulePath in absoluteFiles) - return {path: fullModulePath, pluginData: {inMemory: true}} + if (fullModulePath in absoluteFiles) { + return { + path: fullModulePath, + pluginData: { + inMemory: true, + contents: absoluteFiles[fullModulePath], + }, + } + } } // Return an empty object so that esbuild will handle resolving the file itself. @@ -77,50 +91,30 @@ async function bundleMDX( build.onLoad({filter: /.*/}, async ({path: filePath, pluginData}) => { if (pluginData === undefined || !pluginData.inMemory) { // Return an empty object so that esbuild will load & parse the file contents itself. - return {} + return null } // the || .js allows people to exclude a file extension const fileType = (path.extname(filePath) || '.jsx').slice(1) const contents = absoluteFiles[filePath] - switch (fileType) { - case 'mdx': { - /** @type import('xdm/lib/compile').VFileCompatible */ - const vFileCompatible = { - path: filePath, - contents, - } - const vfile = await compileMDX( - vFileCompatible, - xdmOptions(vFileCompatible, { - jsx: true, - remarkPlugins: [ - remarkFrontmatter, - [remarkMdxFrontmatter, {name: 'frontmatter'}], - ], - }), - ) - return {contents: vfile.toString(), loader: 'jsx'} - } - default: { - /** @type import('esbuild').Loader */ - let loader - - if ( - build.initialOptions.loader && - build.initialOptions.loader[`.${fileType}`] - ) { - loader = build.initialOptions.loader[`.${fileType}`] - } else { - loader = /** @type import('esbuild').Loader */ (fileType) - } + if (fileType === 'mdx') return null - return { - contents, - loader, - } - } + /** @type import('esbuild').Loader */ + let loader + + if ( + build.initialOptions.loader && + build.initialOptions.loader[`.${fileType}`] + ) { + loader = build.initialOptions.loader[`.${fileType}`] + } else { + loader = /** @type import('esbuild').Loader */ (fileType) + } + + return { + contents, + loader, } }) }, @@ -147,14 +141,14 @@ async function bundleMDX( // eslint-disable-next-line babel/new-cap NodeResolvePlugin({extensions: ['.js', '.ts', '.jsx', '.tsx']}), inMemoryPlugin, - // NOTE: the only time the xdm esbuild plugin will be used - // is if it's not processed by our inMemory plugin which will - // only happen for mdx files imported from node_modules. - // This is an edge case, but it's easy enough to support so we do. - // If someone wants to customize *this* particular xdm compilation, - // they'll need to use the esbuildOptions function to swap this - // for their own configured version of this plugin. - xdmESBuild(), + xdmESBuild( + xdmOptions({ + remarkPlugins: [ + remarkFrontmatter, + [remarkMdxFrontmatter, {name: 'frontmatter'}], + ], + }), + ), ], bundle: true, format: 'iife', diff --git a/src/types.d.ts b/src/types.d.ts index 521a0a5..02cf7a7 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -6,7 +6,7 @@ import type {Plugin, BuildOptions, Loader} from 'esbuild' import type {ModuleInfo} from '@fal-works/esbuild-plugin-global-externals' -import type {VFileCompatible, CompileOptions} from 'xdm/lib/compile' +import type {CoreProcessorOptions} from 'xdm/lib/compile' type ESBuildOptions = BuildOptions @@ -45,7 +45,7 @@ type BundleMDXOptions = { * @example * ``` * bundleMDX(mdxString, { - * xdmOptions(input, options) { + * xdmOptions(options) { * // this is the recommended way to add custom remark/rehype plugins: * // The syntax might look weird, but it protects you in case we add/remove * // plugins in the future. @@ -57,10 +57,7 @@ type BundleMDXOptions = { * }) * ``` */ - xdmOptions?: ( - vfileCompatible: VFileCompatible, - options: CompileOptions, - ) => CompileOptions + xdmOptions?: (options: CoreProcessorOptions) => CoreProcessorOptions /** * This allows you to modify the built-in esbuild configuration. This can be * especially helpful for specifying the compilation target.