diff --git a/README.md b/README.md index 423d87d..09f4373 100644 --- a/README.md +++ b/README.md @@ -187,11 +187,13 @@ the esbuild version mdx-bundler uses. - [Installation](#installation) - [Usage](#usage) - [Options](#options) + - [Returns](#returns) + - [Types](#types) - [Component Substitution](#component-substitution) - [Frontmatter and const](#frontmatter-and-const) - [Accessing named exports](#accessing-named-exports) - [Image Bundling](#image-bundling) - - [bundleMDXFile](#bundlemdxfile) + - [Bundling a file.](#bundling-a-file) - [Known Issues](#known-issues) - [Inspiration](#inspiration) - [Other Solutions](#other-solutions) @@ -236,7 +238,8 @@ Here's a **neat** demo: `.trim() -const result = await bundleMDX(mdxSource, { +const result = await bundleMDX({ + source: mdxSource, files: { './demo.tsx': ` import * as React from 'react' @@ -297,6 +300,19 @@ Ultimately, this gets rendered (basically): ### Options +#### source + +The `string` source of your MDX. + +_Can not be set if `file` is set_ + +#### file + +The path to the file on your disk with the MDX in. You will probabbly want to +set [cwd](#cwd) as well. + +_Can not be set if `source` is set_ + #### files The `files` config is an object of all the files you're bundling. The key is the @@ -311,9 +327,12 @@ 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. +The function is passed the default xdmOptions and the frontmatter. + ```ts -bundleMDX(mdxString, { - xdmOptions(options) { +bundleMDX({ + source: mdxSource, + xdmOptions(options, frontmatter) { // 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. @@ -328,12 +347,13 @@ bundleMDX(mdxString, { #### esbuildOptions You can customize any of esbuild options with the option `esbuildOptions`. This -takes a function which is passed the default esbuild options and expects an -options object to be returned. +takes a function which is passed the default esbuild options and the frontmatter +and expects an options object to be returned. ```typescript -bundleMDX(mdxSource, { - esbuildOptions(options) { +bundleMDX({ + source: mdxSource, + esbuildOptions(options, frontmatter) { options.minify = false options.target = [ 'es2020', @@ -366,7 +386,8 @@ and once for this MDX component). This is wasteful and you'd be better off just telling esbuild to _not_ bundle `d3` and you can pass it to the component yourself when you call `getMDXComponent`. -Global external configuration options: https://www.npmjs.com/package/@fal-works/esbuild-plugin-global-externals +Global external configuration options: +https://www.npmjs.com/package/@fal-works/esbuild-plugin-global-externals Here's an example: @@ -382,7 +403,8 @@ import leftPad from 'left-pad'
{leftPad("Neat demo!", 12, '!')}
`.trim() -const result = await bundleMDX(mdxSource, { +const result = await bundleMDX({ + source: mdxSource, // NOTE: this is *only* necessary if you want to share deps between your MDX // file bundle and the host app. Otherwise, all deps will just be bundled. // So it'll work either way, this is just an optimization to avoid sending @@ -449,7 +471,8 @@ Here's a **neat** demo: `.trim() -const result = await bundleMDX(mdxSource, { +const result = await bundleMDX({ + source: mdxSource, cwd: '/users/you/site/_content/pages', }) @@ -465,7 +488,7 @@ Your function is passed the current gray-matter configuration for you to modify. Return your modified configuration object for gray matter. ```js -bundleMDX(mdxString, { +bundleMDX({ grayMatterOptions: options => { options.excerpt = true @@ -474,6 +497,46 @@ bundleMDX(mdxString, { }) ``` +#### bundleDirectory & bundlePath + +This allows you to set the output directory for the bundle and the public URL to +the directory. If one option is set the other must be aswell. + +_The Javascript bundle is not written to this directory and is still returned as +a string from `bundleMDX`._ + +This feature is best used with tweaks to `xdmOptions` and `esbuildOptions`. In +the example below and `.png` files are written to the disk and then served from +`/file/`. + +This allows you to store assets with your MDX and then have esbuild process them +like anything else. + +_It is reccomended that each bundle has its own `bundleDirectory` so that +multiple bundles don't overwrite each others assets._ + +```ts +const {code} = await bundleMDX({ + file: '/path/to/site/content/file.mdx', + cwd: '/path/to/site/content', + bundleDirectory: '/path/to/site/public/file, + bundlePath: '/file/', + xdmOptions: options => { + options.remarkPlugins = [remarkMdxImages] + + return options + }, + esbuildOptions: options => { + options.loader = { + ...options.loader, + '.png': 'file', + } + + return options + }, +}) +``` + ### Returns `bundleMDX` returns a promise for an object with the following properties. @@ -483,6 +546,21 @@ bundleMDX(mdxString, { - `matter` - The whole [object returned by gray-matter](https://github.com/jonschlinkert/gray-matter#returned-object) +### Types + +`mdx-bundler` supplies complete typings within its own package. + +`bundleMDX` has a single type parameter which is the type of your frontmatter. +It defaults to `{[key: string]: any}` and must be an object. This is then used +to type the returned `frontmatter` and the frontmatter passed to +`esbuildOptions` and `xdmOptions`. + +```ts +const {frontmatter} = bundleMDX<{title: string}>({source}) + +frontmatter.title // has type string +``` + ### Component Substitution MDX Bundler passes on @@ -532,17 +610,16 @@ export const exampleImage = 'https://example.com/image.jpg' ### Accessing named exports -You can use `getMDXExport` instead of `getMDXComponent` to treat the mdx file as a module instead of just a component. -It takes the same arguments that `getMDXComponent` does. +You can use `getMDXExport` instead of `getMDXComponent` to treat the mdx file as +a module instead of just a component. It takes the same arguments that +`getMDXComponent` does. ```mdx --- title: Example Post --- -export const toc = [ - { depth: 1, value: 'The title' } -] +export const toc = [{depth: 1, value: 'The title'}] # The title ``` @@ -560,6 +637,7 @@ function MDXPage({code}: {code: string}) { return } ``` + ### Image Bundling With the [cwd](#cwd) and the remark plugin @@ -572,7 +650,8 @@ which outputs the images as inline data urls in the returned code. ```js import {remarkMdxImages} from 'remark-mdx-images' -const {code} = await bundleMDX(mdxSource, { +const {code} = await bundleMDX({ + source: mdxSource, cwd: '/users/you/site/_content/pages', xdmOptions: options => { options.remarkPlugins = [...(options.remarkPlugins ?? []), remarkMdxImages] @@ -602,7 +681,8 @@ folder to be used in image sources. ```js // For the file `_content/pages/about.mdx` -const {code} = await bundleMDX(mdxSource, { +const {code} = await bundleMDX({ + source: mdxSource, cwd: '/users/you/site/_content/pages', xdmOptions: options => { options.remarkPlugins = [...(options.remarkPlugins ?? []), remarkMdxImages] @@ -628,24 +708,22 @@ const {code} = await bundleMDX(mdxSource, { }) ``` -### bundleMDXFile +### Bundling a file. If your MDX file is on your disk you can save some time and code by having -`esbuild` read the file for you. To do this mdx-bundler provides the function -`bundleMDXFile` which works the same as `bundleMDX` except it's first option is -the path to the mdx file instead of the mdx source. +`mdx-bundler` read the file for you. Instead of supplying a `source` string you +can set `file` to the path of the MDX on disk. Set `cwd` to it's folder so that +relative imports work. ```js -import {bundleMDXFile} from 'mdx-bundler' +import {bundleMDX} from 'mdx-bundler' -const {code, frontmatter} = await bundleMDXFile( - '/users/you/site/content/file.mdx', -) +const {code, frontmatter} = await bundleMDX({ + file: '/users/you/site/content/file.mdx', + cwd: '/users/you/site/content/', +}) ``` -`cwd` will be automatically set to the `dirname` of the given file path, you can -still override this. All other options work the same as they do for `bundleMDX`. - ### Known Issues #### Cloudflare Workers diff --git a/package.json b/package.json index eca93b1..2e70444 100644 --- a/package.json +++ b/package.json @@ -40,14 +40,14 @@ "validate": "kcd-scripts validate" }, "dependencies": { - "@babel/runtime": "^7.15.4", + "@babel/runtime": "^7.16.3", "@esbuild-plugins/node-resolve": "^0.1.4", "@fal-works/esbuild-plugin-global-externals": "^2.1.2", "gray-matter": "^4.0.3", "remark-frontmatter": "^4.0.1", - "remark-mdx-frontmatter": "^1.0.1", + "remark-mdx-frontmatter": "^1.1.1", "uuid": "^8.3.2", - "xdm": "^3.2.0" + "xdm": "^3.3.0" }, "peerDependencies": { "esbuild": "0.11.x || 0.12.x || 0.13.x" @@ -61,8 +61,8 @@ "@types/uuid": "^8.3.1", "c8": "^7.10.0", "cross-env": "^7.0.3", - "esbuild": "^0.13.12", - "jsdom": "^18.0.1", + "esbuild": "^0.13.13", + "jsdom": "^18.1.0", "kcd-scripts": "^11.2.2", "left-pad": "^1.3.0", "mdx-test-data": "^1.0.1", diff --git a/src/__tests__/index.js b/src/__tests__/index.js index bbfa9e2..6f78d09 100644 --- a/src/__tests__/index.js +++ b/src/__tests__/index.js @@ -1,13 +1,12 @@ import './setup-tests.js' import path from 'path' -import {fileURLToPath} from 'url' import {test} from 'uvu' import * as assert from 'uvu/assert' import React from 'react' import rtl from '@testing-library/react' import leftPad from 'left-pad' import {remarkMdxImages} from 'remark-mdx-images' -import {bundleMDX, bundleMDXFile} from '../index.js' +import {bundleMDX} from '../index.js' import {getMDXComponent, getMDXExport} from '../client.js' const {render} = rtl @@ -29,7 +28,8 @@ Here's a **neat** demo: `.trim() - const result = await bundleMDX(mdxSource, { + const result = await bundleMDX({ + source: mdxSource, files: { './demo.tsx': ` import * as React from 'react' @@ -120,7 +120,8 @@ import Demo from './demo' `.trim() - const result = await bundleMDX(mdxSource, { + const result = await bundleMDX({ + source: mdxSource, files: { './demo.tsx': ` import leftPad from 'left-pad' @@ -144,7 +145,8 @@ import Demo from './demo' `.trim() const error = /** @type Error */ ( - await bundleMDX(mdxSource, { + await bundleMDX({ + source: mdxSource, files: {}, }).catch(e => e) ) @@ -160,7 +162,8 @@ import Demo from './demo' `.trim() const error = /** @type Error */ ( - await bundleMDX(mdxSource, { + await bundleMDX({ + source: mdxSource, files: { './demo.tsx': `import './blah-blah'`, }, @@ -182,7 +185,8 @@ import Demo from './demo.blah' `.trim() const error = /** @type Error */ ( - await bundleMDX(mdxSource, { + await bundleMDX({ + source: mdxSource, files: { './demo.blah': `what even is this?`, }, @@ -196,7 +200,7 @@ import Demo from './demo.blah' }) test('files is optional', async () => { - await bundleMDX('hello') + await bundleMDX({source: 'hello'}) }) test('uses the typescript loader where needed', async () => { @@ -206,7 +210,8 @@ import Demo from './demo' `.trim() - const {code} = await bundleMDX(mdxSource, { + const {code} = await bundleMDX({ + source: mdxSource, files: { './demo.tsx': ` import * as React from 'react' @@ -241,7 +246,8 @@ import LeftPad from 'left-pad-js' Hi `.trim() - const {code} = await bundleMDX(mdxSource, { + const {code} = await bundleMDX({ + source: mdxSource, files: { 'left-pad-js': `export default () =>
this is left pad
`, }, @@ -273,7 +279,8 @@ export const Demo: React.FC = () => { `.trim(), } - const {code} = await bundleMDX(mdxSource, { + const {code} = await bundleMDX({ + source: mdxSource, files, esbuildOptions: options => { options.loader = { @@ -303,7 +310,8 @@ import {Sample} from './sample-component' ![A Sample Image](./150.png) `.trim() - const {code} = await bundleMDX(mdxSource, { + const {code} = await bundleMDX({ + source: mdxSource, cwd: path.join(process.cwd(), 'other'), xdmOptions: options => { options.remarkPlugins = [remarkMdxImages] @@ -341,21 +349,21 @@ test('should output assets', async () => { ![Sample Image](./150.png) `.trim() - const {code} = await bundleMDX(mdxSource, { + const {code} = await bundleMDX({ + source: mdxSource, cwd: path.join(process.cwd(), 'other'), + bundleDirectory: path.join(process.cwd(), 'output'), + bundlePath: '/img/', xdmOptions: options => { options.remarkPlugins = [remarkMdxImages] return options }, esbuildOptions: options => { - options.outdir = path.join(process.cwd(), 'output') options.loader = { ...options.loader, '.png': 'file', } - options.publicPath = '/img/' - options.write = true return options }, @@ -367,8 +375,9 @@ test('should output assets', async () => { assert.match(container.innerHTML, 'src="/img/150') - const error = /** @type Error */ ( - await bundleMDX(mdxSource, { + const writeError = /** @type Error */ ( + await bundleMDX({ + source: mdxSource, cwd: path.join(process.cwd(), 'other'), xdmOptions: options => { options.remarkPlugins = [remarkMdxImages] @@ -389,9 +398,22 @@ test('should output assets', async () => { ) assert.equal( - error.message, + writeError.message, "You must either specify `write: false` or `write: true` and `outdir: '/path'` in your esbuild options", ) + + const optionError = /** @type Error */ ( + await bundleMDX({ + source: mdxSource, + cwd: path.join(process.cwd(), 'other'), + bundleDirectory: path.join(process.cwd(), 'output'), + }).catch(e => e) + ) + + assert.equal( + optionError.message, + 'When using `bundleDirectory` or `bundlePath` the other must be set.', + ) }) test('should support importing named exports', async () => { @@ -407,7 +429,7 @@ export const uncle = 'Bob' # {uncle} was indeed the uncle `.trim() - const result = await bundleMDX(mdxSource) + const result = await bundleMDX({source: mdxSource}) /** @type {import('../types').MDXExport<{uncle: string}, {title: string, published: Date, description: string}>} */ const mdxExport = getMDXExport(result.code) @@ -435,7 +457,7 @@ Local Content `.trim() - const {code} = await bundleMDX(mdxSource, {}) + const {code} = await bundleMDX({source: mdxSource}) const Component = getMDXComponent(code) @@ -447,32 +469,6 @@ Local Content ) }) -test('should support over-riding the entry point', async () => { - const {code} = await bundleMDX('', { - cwd: process.cwd(), - esbuildOptions: options => { - options.entryPoints = [ - path.join( - path.dirname(fileURLToPath(import.meta.url)), - '..', - '..', - 'CONTRIBUTING.md', - ), - ] - options.outdir = path.join(process.cwd(), 'output') - options.write = true - - return options - }, - }) - - const Component = getMDXComponent(code) - - const {container} = render(React.createElement(Component)) - - assert.match(container.innerHTML, 'Thanks for being willing to contribute') -}) - test('should work with react-dom api', async () => { const mdxSource = ` import Demo from './demo' @@ -480,7 +476,8 @@ import Demo from './demo' `.trim() - const result = await bundleMDX(mdxSource, { + const result = await bundleMDX({ + source: mdxSource, files: { './demo.tsx': ` import * as ReactDOM from 'react-dom' @@ -521,7 +518,8 @@ This is the rest of the content `.trim() - const {matter} = await bundleMDX(mdxSource, { + const {matter} = await bundleMDX({ + source: mdxSource, grayMatterOptions: options => { options.excerpt = true @@ -532,22 +530,38 @@ This is the rest of the content assert.equal((matter.excerpt ? matter.excerpt : '').trim(), 'Some excerpt') }) -test('specify a file using bundleMDXFile', async () => { - const {frontmatter} = await bundleMDXFile( - path.join(process.cwd(), 'other', 'sample.mdx'), - { - esbuildOptions: options => { - options.loader = { - ...options.loader, - '.png': 'dataurl', - } +test('specify a file using bundleMDX', async () => { + const {frontmatter} = await bundleMDX({ + file: path.join(process.cwd(), 'other', 'sample.mdx'), + cwd: path.join(process.cwd(), 'other'), + esbuildOptions: options => { + options.loader = { + ...options.loader, + '.png': 'dataurl', + } - return options - }, + return options }, - ) + }) assert.equal(frontmatter.title, 'Sample') }) +test('let you use the front matter in config', async () => { + await bundleMDX({ + file: path.join(process.cwd(), 'other', 'sample.mdx'), + cwd: path.join(process.cwd(), 'other'), + esbuildOptions: (options, frontmatter) => { + assert.equal(frontmatter.title, 'Sample') + + options.loader = { + ...options.loader, + '.png': 'dataurl', + } + + return options + }, + }) +}) + test.run() diff --git a/src/index.js b/src/index.js index 01bab54..0896559 100644 --- a/src/index.js +++ b/src/index.js @@ -12,22 +12,22 @@ import dirnameMessedUp from './dirname-messed-up.cjs' const {readFile, unlink} = fs.promises /** - * - * @param {string} mdxSource - A string of mdx source code - * @param {import('./types').BundleMDXOptions} options + * @template {{[key: string]: any}} Frontmatter + * @param {import('./types').BundleMDX} options * @returns */ -async function bundleMDX( - mdxSource, - { - files = {}, - xdmOptions = options => options, - esbuildOptions = options => options, - globals = {}, - cwd = path.join(process.cwd(), `__mdx_bundler_fake_dir__`), - grayMatterOptions = options => options, - } = {}, -) { +async function bundleMDX({ + file, + source, + files = {}, + xdmOptions = options => options, + esbuildOptions = options => options, + globals = {}, + cwd = path.join(process.cwd(), `__mdx_bundler_fake_dir__`), + grayMatterOptions = options => options, + bundleDirectory, + bundlePath, +}) { /* c8 ignore start */ if (dirnameMessedUp && !process.env.ESBUILD_BINARY_PATH) { console.warn( @@ -38,18 +38,45 @@ 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 [ - {default: xdmESBuild}, - {default: remarkFrontmatter}, - ] = await Promise.all([ - import('xdm/esbuild.js'), - import('remark-frontmatter'), - ]) + const [{default: xdmESBuild}, {default: remarkFrontmatter}] = + await Promise.all([import('xdm/esbuild.js'), import('remark-frontmatter')]) - const entryPath = path.join(cwd, `./_mdx_bundler_entry_point-${uuid()}.mdx`) + let /** @type string */ code, + /** @type string */ entryPath, + /** @type Omit, "data"> & {data: Frontmatter} */ matter /** @type Record */ - const absoluteFiles = {[entryPath]: mdxSource} + const absoluteFiles = {} + + const isWriting = typeof bundleDirectory === 'string' + + if (typeof bundleDirectory !== typeof bundlePath) { + throw new Error( + 'When using `bundleDirectory` or `bundlePath` the other must be set.', + ) + } + + if (typeof source === 'string') { + // The user has supplied MDX source. + /** @type any */ // Slight type hack to get the graymatter front matter typed correctly. + const gMatter = grayMatter(source, grayMatterOptions({})) + matter = gMatter + entryPath = path.join(cwd, `./_mdx_bundler_entry_point-${uuid()}.mdx`) + absoluteFiles[entryPath] = source + } else if (typeof file === 'string') { + // The user has supplied a file. + /** @type any */ // Slight type hack to get the graymatter front matter typed correctly. + const gMatter = grayMatter.read(file, grayMatterOptions({})) + matter = gMatter + entryPath = file + /* c8 ignore start */ + } else { + // The user supplied neither file or source. + // The typings should prevent reaching this point. + // It is ignored from coverage as the tests wouldn't run in a way that can get here. + throw new Error('`source` or `file` must be defined') + } + /* c8 ignore end*/ for (const [filepath, fileCode] of Object.entries(files)) { absoluteFiles[path.join(cwd, filepath)] = fileCode @@ -125,136 +152,86 @@ async function bundleMDX( }, } - const buildOptions = esbuildOptions({ - entryPoints: [entryPath], - write: false, - absWorkingDir: cwd, - define: { - 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), - }, - plugins: [ - globalExternals({ - ...globals, - react: { - varName: 'React', - type: 'cjs', - }, - 'react-dom': { - varName: 'ReactDOM', - type: 'cjs', - }, - 'react/jsx-runtime': { - varName: '_jsx_runtime', - type: 'cjs', - }, - }), - // eslint-disable-next-line @babel/new-cap - NodeResolvePlugin({ - extensions: ['.js', '.ts', '.jsx', '.tsx'], - resolveOptions: {basedir: cwd}, - }), - inMemoryPlugin, - xdmESBuild( - xdmOptions({ - remarkPlugins: [ - remarkFrontmatter, - [remarkMdxFrontmatter, {name: 'frontmatter'}], - ], + const buildOptions = esbuildOptions( + { + entryPoints: [entryPath], + write: isWriting, + outdir: isWriting ? bundleDirectory : undefined, + publicPath: isWriting ? bundlePath : undefined, + absWorkingDir: cwd, + define: { + 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), + }, + plugins: [ + globalExternals({ + ...globals, + react: { + varName: 'React', + type: 'cjs', + }, + 'react-dom': { + varName: 'ReactDOM', + type: 'cjs', + }, + 'react/jsx-runtime': { + varName: '_jsx_runtime', + type: 'cjs', + }, }), - ), - ], - bundle: true, - format: 'iife', - globalName: 'Component', - minify: true, - }) - - // Extract the front matter from the source or the entry point - - /** @type grayMatter.GrayMatterFile */ - let matter - - // We have to be a bit specific here to ensure type safety - if ( - buildOptions.entryPoints && - Array.isArray(buildOptions.entryPoints) && - buildOptions.entryPoints[0] !== entryPath - ) { - //The user has replaced the entrypoint, we can assume this means `mdxSource` is empty - - matter = grayMatter.read(buildOptions.entryPoints[0], grayMatterOptions({})) - } else { - matter = grayMatter(mdxSource, grayMatterOptions({})) - } + // eslint-disable-next-line @babel/new-cap + NodeResolvePlugin({ + extensions: ['.js', '.ts', '.jsx', '.tsx'], + resolveOptions: {basedir: cwd}, + }), + inMemoryPlugin, + xdmESBuild( + xdmOptions( + { + remarkPlugins: [ + remarkFrontmatter, + [remarkMdxFrontmatter, {name: 'frontmatter'}], + ], + }, + matter.data, + ), + ), + ], + bundle: true, + format: 'iife', + globalName: 'Component', + minify: true, + }, + matter.data, + ) const bundled = await esbuild.build(buildOptions) if (bundled.outputFiles) { const decoder = new StringDecoder('utf8') - const code = decoder.write(Buffer.from(bundled.outputFiles[0].contents)) - - return { - code: `${code};return Component;`, - frontmatter: matter.data, - errors: bundled.errors, - matter, - } - } - - if (buildOptions.outdir && buildOptions.write) { + code = decoder.write(Buffer.from(bundled.outputFiles[0].contents)) + } else if (buildOptions.outdir && buildOptions.write) { // We know that this has to be an array of entry point strings, with a single entry const entryFile = /** @type {{entryPoints: string[]}} */ (buildOptions) .entryPoints[0] const fileName = path.basename(entryFile).replace(/\.[^/.]+$/, '.js') - const code = await readFile(path.join(buildOptions.outdir, fileName)) + code = (await readFile(path.join(buildOptions.outdir, fileName))).toString() await unlink(path.join(buildOptions.outdir, fileName)) - - return { - code: `${code};return Component`, - frontmatter: matter.data, - errors: bundled.errors, - matter, - } + } else { + throw new Error( + "You must either specify `write: false` or `write: true` and `outdir: '/path'` in your esbuild options", + ) } - throw new Error( - "You must either specify `write: false` or `write: true` and `outdir: '/path'` in your esbuild options", - ) -} - -/** - * - * @param {string} mdxPath - The file path to bundle. - * @param {import('./types').BundleMDXOptions} options - * @returns - */ -async function bundleMDXFile( - mdxPath, - { - files = {}, - xdmOptions = options => options, - esbuildOptions = options => options, - globals = {}, - cwd, - grayMatterOptions = options => options, - } = {}, -) { - return bundleMDX('', { - files, - xdmOptions, - esbuildOptions: options => { - options.entryPoints = [mdxPath] - - return esbuildOptions(options) - }, - globals, - cwd: cwd ? cwd : path.dirname(mdxPath), - grayMatterOptions, - }) + return { + code: `${code};return Component;`, + frontmatter: matter.data, + errors: bundled.errors, + matter, + } } -export {bundleMDX, bundleMDXFile} +export {bundleMDX} diff --git a/src/types.d.ts b/src/types.d.ts index 867f946..011236d 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -7,17 +7,38 @@ import type {Plugin, BuildOptions, Loader} from 'esbuild' import type {ModuleInfo} from '@fal-works/esbuild-plugin-global-externals' import type {CoreProcessorOptions} from 'xdm/lib/compile' -import type {GrayMatterOption, Input} from 'gray-matter' +import type {GrayMatterOption, Input, GrayMatterFile} from 'gray-matter' type ESBuildOptions = BuildOptions -type BundleMDXOptions = { +export type BundleMDX = + | BundleMDXSource + | BundleMDXFile + +export type BundleMDXSource = { + /** + * Your MDX source. + */ + source: string + file?: undefined +} & BundleMDXOptions + +export type BundleMDXFile = { + /** + * The path to the mdx file on disk. + */ + file: string + source?: undefined +} & BundleMDXOptions + +type BundleMDXOptions = { /** * The dependencies of the MDX code to be bundled * * @example * ``` - * bundleMDX(mdxString, { + * bundleMDX({ + * source: mdxString, * files: { * './components.tsx': ` * import * as React from 'react' @@ -45,7 +66,8 @@ type BundleMDXOptions = { * * @example * ``` - * bundleMDX(mdxString, { + * bundleMDX({ + * source: mdxString, * 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 @@ -58,14 +80,18 @@ type BundleMDXOptions = { * }) * ``` */ - xdmOptions?: (options: CoreProcessorOptions) => CoreProcessorOptions + xdmOptions?: ( + options: CoreProcessorOptions, + frontmatter: Frontmatter, + ) => CoreProcessorOptions /** * This allows you to modify the built-in esbuild configuration. This can be * especially helpful for specifying the compilation target. * * @example * ``` - * bundleMDX(mdxString, { + * bundleMDX({ + * source: mdxString, * esbuildOptions(options) { * options.target = [ * 'es2020', @@ -80,7 +106,10 @@ type BundleMDXOptions = { * }) * ``` */ - esbuildOptions?: (options: ESBuildOptions) => ESBuildOptions + esbuildOptions?: ( + options: ESBuildOptions, + frontmatter: Frontmatter, + ) => ESBuildOptions /** * Any variables you want treated as global variables in the bundling. * @@ -91,11 +120,12 @@ type BundleMDXOptions = { * * @example * ``` - * bundlMDX(mdxString, { + * bundlMDX({ + * source: mdxString, * globals: {'left-pad': 'myLeftPad'}, * }) * - * // then later + * // on the client side * * import leftPad from 'left-pad' * @@ -112,7 +142,8 @@ type BundleMDXOptions = { * * @example * ``` - * bundleMDX(mdxString, { + * bundleMDX({ + * source: mdxString * cwd: '/users/you/site/mdx_root' * }) * ``` @@ -123,7 +154,8 @@ type BundleMDXOptions = { * * @example * ``` - * bundleMDX(mdxString, { + * bundleMDX({ + * source: mdxString, * grayMatterOptions: (options) => { * options.excerpt = true * @@ -135,6 +167,27 @@ type BundleMDXOptions = { grayMatterOptions?: ( options: GrayMatterOption, ) => GrayMatterOption + /** + * This allows you to set the output directory of the bundle. You will need + * to set `bundlePath` as well to give esbuild the public url to the folder. + * + * *Note, the javascrpt bundle will not be placed here, only assets + * that can't be part of the main bundle.* + * + * @example + * ``` + * bundleMDX({ + * file: '/path/to/file.mdx', + * bundleDirectory: '/path/to/bundle' + * bundlePath: '/path/to/public/bundle' + * }) + * ``` + */ + bundleDirectory?: string + /** + * @see bundleDirectory + */ + bundlePath?: string } type MDXExport<