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<