Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use xdmEsBuild for all compiling #45

Merged
merged 5 commits into from May 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
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
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
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
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
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