Skip to content

Commit 3d2a5a5

Browse files
authored
feat: allow write to be true so the file loader can be used (#34)
* feat: allow `write` to be `true` so the file loader can be used * docs: add image bundling to readme * update esbuild to 0.11.16 * docs: add note about node-gyp to the readme, closes #35 * run format and fix typo Closes #35 Closes #26
1 parent 7b8e546 commit 3d2a5a5

File tree

6 files changed

+155
-7
lines changed

6 files changed

+155
-7
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ node_modules
22
coverage
33
dist
44
.DS_Store
5+
output/
56

67
# these cause more harm than good
78
# when working with contributors

README.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,9 @@ should be installed as one of your project's `dependencies`:
195195
npm install --save mdx-bundler
196196
```
197197

198+
One of mdx-bundler's dependancies requires a working [node-gyp][node-gyp] setup
199+
to be able to install correctly.
200+
198201
## Usage
199202

200203
```typescript
@@ -481,6 +484,68 @@ export const exampleImage = 'https://example.com/image.jpg'
481484
<img src={exampleImage} alt="Image alt text" />
482485
```
483486

487+
### Image Bundling
488+
489+
With the [cwd](#cwd) and the remark plugin
490+
[remark-mdx-images](https://www.npmjs.com/package/remark-mdx-images) you can
491+
bundle images in your mdx!
492+
493+
There are two loaders in esbuild that can be used here. The easiest is `dataurl`
494+
which outputs the images as inline data urls in the returned code.
495+
496+
```js
497+
import {remarkMdxImages} from 'remark-mdx-images'
498+
499+
const {code} = await bundleMDX(mdxSource, {
500+
cwd: '/users/you/site/_content/pages',
501+
xdmOptions: (vFile, options) => {
502+
options.remarkPlugins = [remarkMdxImages]
503+
504+
return options
505+
},
506+
esbuildOptions: options => {
507+
options.loader = {
508+
...options.loader,
509+
'.png': 'dataurl',
510+
}
511+
512+
return options
513+
},
514+
})
515+
```
516+
517+
The `file` loader requires a little more configuration to get working. With the
518+
`file` loader your images are copied to the output directory so esbuild needs to
519+
be set to write files and needs to know where to put them plus the url of the
520+
folder to be used in image sources.
521+
522+
```js
523+
const {code} = await bundleMDX(mdxSource, {
524+
cwd: '/users/you/site/_content/pages',
525+
xdmOptions: (vFile, options) => {
526+
options.remarkPlugins = [remarkMdxImages]
527+
528+
return options
529+
},
530+
esbuildOptions: options => {
531+
// Set the `outdir` to your public directory.
532+
options.outdir = '/users/you/site/public/img'
533+
options.loader = {
534+
...options.loader,
535+
// Tell esbuild to use the `file` loader for pngs
536+
'.png': 'file',
537+
}
538+
// Set the public path to /img/ so image sources start /img/
539+
options.publicPath = '/img/'
540+
541+
// Set write to true so that esbuild will output the files.
542+
options.write = true
543+
544+
return options
545+
},
546+
})
547+
```
548+
484549
### Known Issues
485550

486551
#### Cloudflare Workers
@@ -624,4 +689,5 @@ MIT
624689
[bugs]: https://github.com/kentcdodds/mdx-bundler/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+sort%3Acreated-desc+label%3Abug
625690
[requests]: https://github.com/kentcdodds/mdx-bundler/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc+label%3Aenhancement
626691
[good-first-issue]: https://github.com/kentcdodds/mdx-bundler/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc+label%3Aenhancement+label%3A%22good+first+issue%22
692+
[node-gyp]: https://github.com/nodejs/node-gyp#installation
627693
<!-- prettier-ignore-end -->

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
"@babel/runtime": "^7.13.17",
4444
"@esbuild-plugins/node-resolve": "^0.1.4",
4545
"@fal-works/esbuild-plugin-global-externals": "^2.1.1",
46-
"esbuild": "^0.11.15",
46+
"esbuild": "^0.11.16",
4747
"gray-matter": "^4.0.3",
4848
"jsdom": "^16.5.3",
4949
"remark-frontmatter": "^3.0.0",

src/__tests__/index.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import React from 'react'
55
import rtl from '@testing-library/react'
66
import leftPad from 'left-pad'
77
import {remarkMdxImages} from 'remark-mdx-images'
8+
import path from 'path'
89
import {bundleMDX} from '../index.js'
910
import {getMDXComponent} from '../client.js'
1011

@@ -293,4 +294,62 @@ import {Sample} from './other/sample-component'
293294
)
294295
})
295296

297+
test('should output assets', async () => {
298+
const mdxSource = `
299+
# Sample Post
300+
301+
![Sample Image](./other/150.png)
302+
`.trim()
303+
304+
const {code} = await bundleMDX(mdxSource, {
305+
cwd: process.cwd(),
306+
xdmOptions: (vFile, options) => {
307+
options.remarkPlugins = [remarkMdxImages]
308+
309+
return options
310+
},
311+
esbuildOptions: options => {
312+
options.outdir = path.join(process.cwd(), 'output')
313+
options.loader = {
314+
...options.loader,
315+
'.png': 'file',
316+
}
317+
options.publicPath = '/img/'
318+
options.write = true
319+
320+
return options
321+
},
322+
})
323+
324+
const Component = getMDXComponent(code)
325+
326+
const {container} = render(React.createElement(Component))
327+
328+
assert.match(container.innerHTML, 'src="/img/150')
329+
330+
const error = /** @type Error */ (await bundleMDX(mdxSource, {
331+
cwd: process.cwd(),
332+
xdmOptions: (vFile, options) => {
333+
options.remarkPlugins = [remarkMdxImages]
334+
335+
return options
336+
},
337+
esbuildOptions: options => {
338+
options.loader = {
339+
...options.loader,
340+
// esbuild will throw its own error if we try to use `file` loader without `outdir`
341+
'.png': 'dataurl',
342+
}
343+
options.write = true
344+
345+
return options
346+
},
347+
}).catch(e => e))
348+
349+
assert.equal(
350+
error.message,
351+
"You must either specify `write: false` or `write: true` and `outdir: '/path'` in your esbuild options",
352+
)
353+
})
354+
296355
test.run()

src/index.js

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import fs from 'fs'
12
import path from 'path'
23
import {StringDecoder} from 'string_decoder'
34
import remarkFrontmatter from 'remark-frontmatter'
@@ -8,6 +9,8 @@ import {NodeResolvePlugin} from '@esbuild-plugins/node-resolve'
89
import {globalExternals} from '@fal-works/esbuild-plugin-global-externals'
910
import dirnameMessedUp from './dirname-messed-up.cjs'
1011

12+
const {readFile, unlink} = fs.promises
13+
1114
/**
1215
*
1316
* @param {string} mdxSource - A string of mdx source code
@@ -149,14 +152,33 @@ async function bundleMDX(
149152

150153
const bundled = await esbuild.build(buildOptions)
151154

152-
const decoder = new StringDecoder('utf8')
155+
if (bundled.outputFiles) {
156+
const decoder = new StringDecoder('utf8')
153157

154-
const code = decoder.write(Buffer.from(bundled.outputFiles[0].contents))
158+
const code = decoder.write(Buffer.from(bundled.outputFiles[0].contents))
155159

156-
return {
157-
code: `${code};return Component.default;`,
158-
frontmatter,
160+
return {
161+
code: `${code};return Component.default;`,
162+
frontmatter,
163+
}
159164
}
165+
166+
if (buildOptions.outdir && buildOptions.write) {
167+
const code = await readFile(
168+
path.join(buildOptions.outdir, '_mdx_bundler_entry_point.js'),
169+
)
170+
171+
await unlink(path.join(buildOptions.outdir, '_mdx_bundler_entry_point.js'))
172+
173+
return {
174+
code: `${code};return Component.default;`,
175+
frontmatter,
176+
}
177+
}
178+
179+
throw new Error(
180+
"You must either specify `write: false` or `write: true` and `outdir: '/path'` in your esbuild options",
181+
)
160182
}
161183

162184
export {bundleMDX}

src/types.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type {Plugin, BuildOptions, Loader} from 'esbuild'
88
import type {ModuleInfo} from '@fal-works/esbuild-plugin-global-externals'
99
import type {VFileCompatible, CompileOptions} from 'xdm/lib/compile'
1010

11-
type ESBuildOptions = BuildOptions & {write: false}
11+
type ESBuildOptions = BuildOptions
1212

1313
type BundleMDXOptions = {
1414
/**

0 commit comments

Comments
 (0)