Skip to content

Commit

Permalink
feat: use xdmEsBuild for all compiling (#45)
Browse files Browse the repository at this point in the history
Co-authored-by: Gaël Haméon <17253950+gaelhameon@users.noreply.github.com>

BREAKING CHANGE: `xdmOptions` now only accepts a single argument of `options`.
  • Loading branch information
Arcath authored May 10, 2021
1 parent 4664b61 commit 7a61aed
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 71 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,22 +43,23 @@
"@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",
"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",
Expand Down
31 changes: 26 additions & 5 deletions src/__tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"`,
)
})

Expand Down Expand Up @@ -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)`,
)
})

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -390,4 +390,25 @@ test('should output assets', async () => {
)
})

test('should support mdx from node_modules', async () => {
const mdxSource = `
import MdxData from 'mdx-test-data'
Local Content
<MdxData />
`.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()
102 changes: 48 additions & 54 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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__`),
Expand All @@ -35,8 +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'),
const [{default: xdmESBuild}] = await Promise.all([
await import('xdm/esbuild.js'),
])
// extract the frontmatter
Expand All @@ -56,18 +55,33 @@ async function bundleMDX(
name: 'inMemory',
setup(build) {
build.onResolve({filter: /.*/}, ({path: filePath, importer}) => {
if (filePath === entryPath)
return {path: filePath, pluginData: {inMemory: true}}
if (filePath === entryPath) {
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}}
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)
return {path: fullModulePath, pluginData: {inMemory: true}}
if (fullModulePath in absoluteFiles) {
return {
path: fullModulePath,
pluginData: {
inMemory: true,
contents: absoluteFiles[fullModulePath],
},
}
}
}

// Return an empty object so that esbuild will handle resolving the file itself.
Expand All @@ -77,50 +91,30 @@ 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
const fileType = (path.extname(filePath) || '.jsx').slice(1)
const contents = absoluteFiles[filePath]

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'}
}
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,
}
})
},
Expand All @@ -147,14 +141,14 @@ 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(),
xdmESBuild(
xdmOptions({
remarkPlugins: [
remarkFrontmatter,
[remarkMdxFrontmatter, {name: 'frontmatter'}],
],
}),
),
],
bundle: true,
format: 'iife',
Expand Down
9 changes: 3 additions & 6 deletions src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,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'
import type {CoreProcessorOptions} from 'xdm/lib/compile'

type ESBuildOptions = BuildOptions

Expand Down Expand Up @@ -45,7 +45,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.
Expand All @@ -57,10 +57,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.
Expand Down

0 comments on commit 7a61aed

Please sign in to comment.