Skip to content

Commit

Permalink
feat: mdx prepare
Browse files Browse the repository at this point in the history
  • Loading branch information
zce committed Nov 15, 2023
1 parent 2c11070 commit 097ed18
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 7 deletions.
11 changes: 7 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,20 @@
},
"prettier": "@zce/prettier-config",
"dependencies": {
"@esbuild-plugins/node-resolve": "latest",
"@fal-works/esbuild-plugin-global-externals": "latest",
"@mdx-js/esbuild": "latest",
"cac": "latest",
"esbuild": "latest",
"fast-glob": "latest",
"unist-util-visit": "latest",
"unified": "latest",
"rehype-raw": "latest",
"rehype-stringify": "latest",
"remark-gfm": "latest",
"remark-parse": "latest",
"remark-rehype": "latest",
"rehype-raw": "latest",
"rehype-stringify": "latest",
"sharp": "latest",
"unified": "latest",
"unist-util-visit": "latest",
"vfile": "latest",
"vfile-reporter": "latest",
"yaml": "latest",
Expand Down
5 changes: 2 additions & 3 deletions src/shared/markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ const getBuiltInPlugins = (plugins?: BuiltinPlugins) => {
})
}

export const markdown = (options: MarkdownOptions = {}) => {
return z.string().transform(async (value, ctx): Promise<MarkdownBody> => {
export const markdown = (options: MarkdownOptions = {}) =>
z.string().transform(async (value, ctx): Promise<MarkdownBody> => {
const file = await unified()
.use(remarkParse) // Parse markdown content to a syntax tree
.use(remarkGfm) // Support GFM (autolink literals, footnotes, strikethrough, tables, tasklists).
Expand Down Expand Up @@ -74,4 +74,3 @@ export const markdown = (options: MarkdownOptions = {}) => {
html: file.toString()
}
})
}
171 changes: 171 additions & 0 deletions src/shared/mdx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
// https://github.com/Modwatch/Frontend/blob/02fa6aca8341cc4eb4307272b66364c33f520429/generatepostmeta.ts#L68

import { dirname, extname, isAbsolute, join, resolve } from 'node:path'
import { StringDecoder } from 'node:string_decoder'
import { NodeResolvePlugin } from '@esbuild-plugins/node-resolve'
import { globalExternals } from '@fal-works/esbuild-plugin-global-externals'
import mdxPlugin from '@mdx-js/esbuild'
import { build } from 'esbuild'
import { VFile } from 'vfile'
import z from 'zod'

import type { ModuleInfo } from '@fal-works/esbuild-plugin-global-externals'
import type { BuildOptions, Loader, Plugin } from 'esbuild'
import type { PluggableList } from 'unified'

interface MdxBody {
// raw: string
plain: string
excerpt: string
code: string
}
interface MdxOptions {
remarkPlugins?: PluggableList
rehypePlugins?: PluggableList
}
export const mdx = (options: MdxOptions = {}) =>
z.string().transform(async (value, ctx): Promise<MdxBody> => {
const path = ctx.path[0] as string
const bundled = await build(await esbuildOptions(file, globals))
const decoder = new StringDecoder('utf8')

if (bundled.outputFiles === undefined || bundled.outputFiles.length === 0) {
throw new Error('Esbuild bundling error')
}

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

return {
code: `${code};return Component`
}

return {
// raw: value,
plain: value as string,
excerpt: value as string,
code: code || ''
}
})

/**
* Mostly derived from MDX Bundler, but strips out a lot of the stuff we don't need as
* well as fix some incompatabilities with esbuild resolution with pnpm and esm plugins.
*/

export interface FrontMatter {
title: string
section: string
description?: string
}
export interface SerialiseOutput {
code: string
frontmatter: FrontMatter
}

export type Globals = Record<string, string>

const esbuildOptions = async (source: VFile, globals: Globals): Promise<BuildOptions> => {
const absoluteFiles: Record<string, string> = {}

const entryPath = source.path ? (isAbsolute(source.path) ? source.path : join(source.cwd, source.path)) : join(source.cwd, `./_mdx_bundler_entry_point-${Math.random()}.mdx`)
absoluteFiles[entryPath] = String(source.value)

// https://github.com/kentcdodds/mdx-bundler/pull/206
const define: BuildOptions['define'] = {}
if (process.env.NODE_ENV !== undefined) {
define['process.env.NODE_ENV'] = JSON.stringify(process.env.NODE_ENV)
}

// Import any imported components into esbuild resolver
const inMemoryPlugin: Plugin = {
name: 'inMemory',
setup(build) {
build.onResolve({ filter: /.*/ }, ({ path: filePath, importer }) => {
if (filePath === entryPath) {
return {
path: filePath,
pluginData: { inMemory: true, contents: absoluteFiles[filePath] }
}
}

const modulePath = resolve(dirname(importer), filePath)

if (modulePath in absoluteFiles) {
return {
path: modulePath,
pluginData: {
inMemory: true,
contents: absoluteFiles[modulePath]
}
}
}

for (const ext of ['.js', '.ts', '.jsx', '.tsx', '.json', '.mdx', '.css']) {
const fullModulePath = `${modulePath}${ext}`
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.
return {}
})

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
}

// the || .js allows people to exclude a file extension
const fileType = (extname(filePath) || '.jsx').slice(1)
const contents = absoluteFiles[filePath]

if (fileType === 'mdx') return

const loader: Loader = build.initialOptions.loader?.[`.${fileType}`] ? build.initialOptions.loader[`.${fileType}`] : (fileType as Loader)

return {
contents,
loader
}
})
}
}

// This helps reduce bundles from having duplicated packages
const newGlobals: Record<string, ModuleInfo> = {}
for (const [key, value] of Object.entries(globals)) {
newGlobals[key] = {
varName: value,
type: 'cjs'
}
}

return {
entryPoints: [entryPath],
write: false,
define,
plugins: [
globalExternals(newGlobals),
NodeResolvePlugin({
extensions: ['.js', '.jsx', '.ts', '.tsx']
}),
inMemoryPlugin,
mdxPlugin({
remarkPlugins: [],
rehypePlugins: []
})
],
bundle: true,
format: 'iife',
globalName: 'Component',
minify: process.env.NODE_ENV === 'production'
}
}
1 change: 1 addition & 0 deletions src/shared/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,4 @@ export const image = () =>
)

export { markdown } from './markdown'
export { mdx } from './mdx'

0 comments on commit 097ed18

Please sign in to comment.