From 45f0664957d917b1363a5a26d81d783c35fda552 Mon Sep 17 00:00:00 2001 From: Arcath Date: Sun, 2 May 2021 14:20:43 +0100 Subject: [PATCH 1/5] feat: create our own xdm plugin that respects inMemory so we have a single parser for all mdx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Gaël Haméon <17253950+gaelhameon@users.noreply.github.com> --- package.json | 1 + src/__tests__/index.js | 12 +++++++ src/index.js | 81 +++++++++++++++++++++++++++--------------- 3 files changed, 66 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index f72afbd..c438b84 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "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..54d2fab 100644 --- a/src/__tests__/index.js +++ b/src/__tests__/index.js @@ -390,4 +390,16 @@ 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, {}) +}) + test.run() diff --git a/src/index.js b/src/index.js index 28ef0e5..497ab01 100644 --- a/src/index.js +++ b/src/index.js @@ -57,17 +57,29 @@ async function bundleMDX( setup(build) { build.onResolve({filter: /.*/}, ({path: filePath, importer}) => { if (filePath === entryPath) - return {path: filePath, pluginData: {inMemory: true}} + 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}} + 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}} + return { + path: fullModulePath, + pluginData: { + inMemory: true, + contents: absoluteFiles[fullModulePath], + }, + } } // Return an empty object so that esbuild will handle resolving the file itself. @@ -77,7 +89,7 @@ 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 @@ -86,22 +98,8 @@ async function bundleMDX( 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'} + // Doing this allows xdmPlugin to handle it + return null } default: { /** @type import('esbuild').Loader */ @@ -126,6 +124,40 @@ async function bundleMDX( }, } + /** @type import('esbuild').Plugin */ + const xdmPlugin = { + name: 'xdm', + setup(build) { + build.onLoad({filter: /\.mdx$/}, async ({path: filePath, pluginData}) => { + /** @type string */ + let fileContents + + if (pluginData !== undefined && pluginData.inMemory) { + fileContents = pluginData.contents + } else { + fileContents = (await readFile(filePath)).toString() + } + + /** @type import('xdm/lib/compile').VFileCompatible */ + const vFileCompatible = { + path: filePath, + contents: fileContents, + } + const vfile = await compileMDX( + vFileCompatible, + xdmOptions(vFileCompatible, { + jsx: true, + remarkPlugins: [ + remarkFrontmatter, + [remarkMdxFrontmatter, {name: 'frontmatter'}], + ], + }), + ) + return {contents: vfile.toString(), loader: 'jsx'} + }) + }, + } + const buildOptions = esbuildOptions({ entryPoints: [entryPath], write: false, @@ -147,14 +179,7 @@ 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(), + xdmPlugin, ], bundle: true, format: 'iife', From ef80ce5f21b861a22aa849cac8902e10ea713e5b Mon Sep 17 00:00:00 2001 From: Arcath Date: Sun, 2 May 2021 14:30:33 +0100 Subject: [PATCH 2/5] chore: fix linting issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Gaël Haméon <17253950+gaelhameon@users.noreply.github.com> --- src/__tests__/index.js | 13 +++++++++++-- src/index.js | 5 +---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/__tests__/index.js b/src/__tests__/index.js index 54d2fab..2144a09 100644 --- a/src/__tests__/index.js +++ b/src/__tests__/index.js @@ -392,14 +392,23 @@ test('should output assets', async () => { test('should support mdx from node_modules', async () => { const mdxSource = ` -import mdxData from 'mdx-test-data' +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 497ab01..f5ecfaa 100644 --- a/src/index.js +++ b/src/index.js @@ -35,10 +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'), - await import('xdm/esbuild.js'), - ]) + const [{compile: compileMDX}] = await Promise.all([await import('xdm')]) // extract the frontmatter const {data: frontmatter} = matter(mdxSource) From 0dd1abaf86f6b2ec6f7ab996cb80de751460bb94 Mon Sep 17 00:00:00 2001 From: Arcath Date: Sun, 2 May 2021 16:25:31 +0100 Subject: [PATCH 3/5] update filter matcher --- src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index f5ecfaa..8e0e139 100644 --- a/src/index.js +++ b/src/index.js @@ -125,7 +125,7 @@ async function bundleMDX( const xdmPlugin = { name: 'xdm', setup(build) { - build.onLoad({filter: /\.mdx$/}, async ({path: filePath, pluginData}) => { + build.onLoad({filter: /\.(md|markdown|mdown|mkdn|mkd|mdwn|mkdown|ron|mdx)$/}, async ({path: filePath, pluginData}) => { /** @type string */ let fileContents From db66ec296f598eff66d8b259b18041df122790fd Mon Sep 17 00:00:00 2001 From: Arcath Date: Mon, 10 May 2021 20:58:17 +0100 Subject: [PATCH 4/5] update to xdm 1.11.0 and use its plugin --- package.json | 6 ++--- src/__tests__/index.js | 10 ++++----- src/index.js | 51 +++++++++++------------------------------- src/types.d.ts | 13 ++++++----- 4 files changed, 28 insertions(+), 52 deletions(-) diff --git a/package.json b/package.json index c438b84..f42784c 100644 --- a/package.json +++ b/package.json @@ -43,18 +43,18 @@ "@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", diff --git a/src/__tests__/index.js b/src/__tests__/index.js index 2144a09..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 diff --git a/src/index.js b/src/index.js index 8e0e139..14ce378 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,7 +35,9 @@ 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}] = await Promise.all([await import('xdm')]) + const [{default: xdmESBuild}] = await Promise.all([ + await import('xdm/esbuild.js'), + ]) // extract the frontmatter const {data: frontmatter} = matter(mdxSource) @@ -95,7 +97,7 @@ async function bundleMDX( switch (fileType) { case 'mdx': { - // Doing this allows xdmPlugin to handle it + // Doing this allows xdmESBuild to handle it return null } default: { @@ -121,40 +123,6 @@ async function bundleMDX( }, } - /** @type import('esbuild').Plugin */ - const xdmPlugin = { - name: 'xdm', - setup(build) { - build.onLoad({filter: /\.(md|markdown|mdown|mkdn|mkd|mdwn|mkdown|ron|mdx)$/}, async ({path: filePath, pluginData}) => { - /** @type string */ - let fileContents - - if (pluginData !== undefined && pluginData.inMemory) { - fileContents = pluginData.contents - } else { - fileContents = (await readFile(filePath)).toString() - } - - /** @type import('xdm/lib/compile').VFileCompatible */ - const vFileCompatible = { - path: filePath, - contents: fileContents, - } - const vfile = await compileMDX( - vFileCompatible, - xdmOptions(vFileCompatible, { - jsx: true, - remarkPlugins: [ - remarkFrontmatter, - [remarkMdxFrontmatter, {name: 'frontmatter'}], - ], - }), - ) - return {contents: vfile.toString(), loader: 'jsx'} - }) - }, - } - const buildOptions = esbuildOptions({ entryPoints: [entryPath], write: false, @@ -176,7 +144,14 @@ async function bundleMDX( // eslint-disable-next-line babel/new-cap NodeResolvePlugin({extensions: ['.js', '.ts', '.jsx', '.tsx']}), inMemoryPlugin, - xdmPlugin, + 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..f5b026a 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -6,7 +6,11 @@ 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 { + VFileCompatible, + CompileOptions, + CoreProcessorOptions, +} from 'xdm/lib/compile' type ESBuildOptions = BuildOptions @@ -45,7 +49,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 +61,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. From 735b82605593082ebc64fe575a26c03853bb1d07 Mon Sep 17 00:00:00 2001 From: Arcath Date: Mon, 10 May 2021 21:08:05 +0100 Subject: [PATCH 5/5] document and address reviews --- README.md | 6 +++--- src/index.js | 47 ++++++++++++++++++++++------------------------- src/types.d.ts | 6 +----- 3 files changed, 26 insertions(+), 33 deletions(-) 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/src/index.js b/src/index.js index 14ce378..04d8ae3 100644 --- a/src/index.js +++ b/src/index.js @@ -55,23 +55,25 @@ async function bundleMDX( name: 'inMemory', setup(build) { build.onResolve({filter: /.*/}, ({path: filePath, importer}) => { - if (filePath === entryPath) + if (filePath === entryPath) { return { path: filePath, pluginData: {inMemory: true, contents: absoluteFiles[filePath]}, } + } const modulePath = path.resolve(path.dirname(importer), filePath) - if (modulePath in absoluteFiles) + 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) + if (fullModulePath in absoluteFiles) { return { path: fullModulePath, pluginData: { @@ -79,6 +81,7 @@ async function bundleMDX( contents: absoluteFiles[fullModulePath], }, } + } } // Return an empty object so that esbuild will handle resolving the file itself. @@ -95,29 +98,23 @@ async function bundleMDX( const fileType = (path.extname(filePath) || '.jsx').slice(1) const contents = absoluteFiles[filePath] - switch (fileType) { - case 'mdx': { - // Doing this allows xdmESBuild to handle it - return null - } - 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, } }) }, diff --git a/src/types.d.ts b/src/types.d.ts index f5b026a..02cf7a7 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -6,11 +6,7 @@ import type {Plugin, BuildOptions, Loader} from 'esbuild' import type {ModuleInfo} from '@fal-works/esbuild-plugin-global-externals' -import type { - VFileCompatible, - CompileOptions, - CoreProcessorOptions, -} from 'xdm/lib/compile' +import type {CoreProcessorOptions} from 'xdm/lib/compile' type ESBuildOptions = BuildOptions