Skip to content

Commit

Permalink
feat: allow write to be true so the file loader can be used (#34)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Arcath authored Apr 29, 2021
1 parent 7b8e546 commit 3d2a5a5
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 7 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ node_modules
coverage
dist
.DS_Store
output/

# these cause more harm than good
# when working with contributors
Expand Down
66 changes: 66 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,9 @@ should be installed as one of your project's `dependencies`:
npm install --save mdx-bundler
```

One of mdx-bundler's dependancies requires a working [node-gyp][node-gyp] setup
to be able to install correctly.

## Usage

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

### Image Bundling

With the [cwd](#cwd) and the remark plugin
[remark-mdx-images](https://www.npmjs.com/package/remark-mdx-images) you can
bundle images in your mdx!

There are two loaders in esbuild that can be used here. The easiest is `dataurl`
which outputs the images as inline data urls in the returned code.

```js
import {remarkMdxImages} from 'remark-mdx-images'

const {code} = await bundleMDX(mdxSource, {
cwd: '/users/you/site/_content/pages',
xdmOptions: (vFile, options) => {
options.remarkPlugins = [remarkMdxImages]

return options
},
esbuildOptions: options => {
options.loader = {
...options.loader,
'.png': 'dataurl',
}

return options
},
})
```

The `file` loader requires a little more configuration to get working. With the
`file` loader your images are copied to the output directory so esbuild needs to
be set to write files and needs to know where to put them plus the url of the
folder to be used in image sources.

```js
const {code} = await bundleMDX(mdxSource, {
cwd: '/users/you/site/_content/pages',
xdmOptions: (vFile, options) => {
options.remarkPlugins = [remarkMdxImages]

return options
},
esbuildOptions: options => {
// Set the `outdir` to your public directory.
options.outdir = '/users/you/site/public/img'
options.loader = {
...options.loader,
// Tell esbuild to use the `file` loader for pngs
'.png': 'file',
}
// Set the public path to /img/ so image sources start /img/
options.publicPath = '/img/'

// Set write to true so that esbuild will output the files.
options.write = true

return options
},
})
```

### Known Issues

#### Cloudflare Workers
Expand Down Expand Up @@ -624,4 +689,5 @@ MIT
[bugs]: https://github.com/kentcdodds/mdx-bundler/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+sort%3Acreated-desc+label%3Abug
[requests]: https://github.com/kentcdodds/mdx-bundler/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc+label%3Aenhancement
[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
[node-gyp]: https://github.com/nodejs/node-gyp#installation
<!-- prettier-ignore-end -->
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"@babel/runtime": "^7.13.17",
"@esbuild-plugins/node-resolve": "^0.1.4",
"@fal-works/esbuild-plugin-global-externals": "^2.1.1",
"esbuild": "^0.11.15",
"esbuild": "^0.11.16",
"gray-matter": "^4.0.3",
"jsdom": "^16.5.3",
"remark-frontmatter": "^3.0.0",
Expand Down
59 changes: 59 additions & 0 deletions src/__tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import React from 'react'
import rtl from '@testing-library/react'
import leftPad from 'left-pad'
import {remarkMdxImages} from 'remark-mdx-images'
import path from 'path'
import {bundleMDX} from '../index.js'
import {getMDXComponent} from '../client.js'

Expand Down Expand Up @@ -293,4 +294,62 @@ import {Sample} from './other/sample-component'
)
})

test('should output assets', async () => {
const mdxSource = `
# Sample Post
![Sample Image](./other/150.png)
`.trim()

const {code} = await bundleMDX(mdxSource, {
cwd: process.cwd(),
xdmOptions: (vFile, 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
},
})

const Component = getMDXComponent(code)

const {container} = render(React.createElement(Component))

assert.match(container.innerHTML, 'src="/img/150')

const error = /** @type Error */ (await bundleMDX(mdxSource, {
cwd: process.cwd(),
xdmOptions: (vFile, options) => {
options.remarkPlugins = [remarkMdxImages]

return options
},
esbuildOptions: options => {
options.loader = {
...options.loader,
// esbuild will throw its own error if we try to use `file` loader without `outdir`
'.png': 'dataurl',
}
options.write = true

return options
},
}).catch(e => e))

assert.equal(
error.message,
"You must either specify `write: false` or `write: true` and `outdir: '/path'` in your esbuild options",
)
})

test.run()
32 changes: 27 additions & 5 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import fs from 'fs'
import path from 'path'
import {StringDecoder} from 'string_decoder'
import remarkFrontmatter from 'remark-frontmatter'
Expand All @@ -8,6 +9,8 @@ import {NodeResolvePlugin} from '@esbuild-plugins/node-resolve'
import {globalExternals} from '@fal-works/esbuild-plugin-global-externals'
import dirnameMessedUp from './dirname-messed-up.cjs'

const {readFile, unlink} = fs.promises

/**
*
* @param {string} mdxSource - A string of mdx source code
Expand Down Expand Up @@ -149,14 +152,33 @@ async function bundleMDX(

const bundled = await esbuild.build(buildOptions)

const decoder = new StringDecoder('utf8')
if (bundled.outputFiles) {
const decoder = new StringDecoder('utf8')

const code = decoder.write(Buffer.from(bundled.outputFiles[0].contents))
const code = decoder.write(Buffer.from(bundled.outputFiles[0].contents))

return {
code: `${code};return Component.default;`,
frontmatter,
return {
code: `${code};return Component.default;`,
frontmatter,
}
}

if (buildOptions.outdir && buildOptions.write) {
const code = await readFile(
path.join(buildOptions.outdir, '_mdx_bundler_entry_point.js'),
)

await unlink(path.join(buildOptions.outdir, '_mdx_bundler_entry_point.js'))

return {
code: `${code};return Component.default;`,
frontmatter,
}
}

throw new Error(
"You must either specify `write: false` or `write: true` and `outdir: '/path'` in your esbuild options",
)
}

export {bundleMDX}
2 changes: 1 addition & 1 deletion src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,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'

type ESBuildOptions = BuildOptions & {write: false}
type ESBuildOptions = BuildOptions

type BundleMDXOptions = {
/**
Expand Down

0 comments on commit 3d2a5a5

Please sign in to comment.