next.js/packages/next-mdx/mdx-rs-loader.js
mdx-rs-loader.js172 lines4.1 KB
const { SourceMapGenerator } = require('source-map')
const path = require('path')
const { createHash } = require('crypto')

const markdownExtensions = [
  'md',
  'markdown',
  'mdown',
  'mkdn',
  'mkd',
  'mdwn',
  'mkdown',
  'ron',
]
const mdx = ['.mdx']
const md = markdownExtensions.map((/** @type {string} */ d) => '.' + d)

const own = {}.hasOwnProperty

const marker = {}
const cache = new WeakMap()

/*
 * From next.config.js's mdxRs option, construct an actual option object that mdxRs compiler accepts.
 */
function coereceMdxTransformOptions(options = {}) {
  const { mdxType, ...restOptions } = options

  let parse = undefined
  switch (mdxType) {
    case 'gfm':
      parse = {
        constructs: {
          gfmAutolinkLiteral: true,
          gfmFootnoteDefinition: true,
          gfmLabelStartFootnote: true,
          gfmStrikethrough: true,
          gfmTable: true,
          gfmTaskListItem: true,
        },
      }
      break
    case 'commonMark':
    default:
      parse = { gfmStrikethroughSingleTilde: true, mathTextSingleDollar: true }
      break
  }

  return {
    ...restOptions,
    parse,
  }
}

/**
 * A webpack loader for mdx-rs. This is largely based on existing @mdx-js/loader,
 * replaces internal compilation logic to use mdx-rs instead.
 */
function loader(value, bindings, callback) {
  const defaults = this.sourceMap ? { SourceMapGenerator } : {}
  const options = this.getOptions()
  const config = { ...defaults, ...options }
  const hash = getOptionsHash(options)
  const compiler = this._compiler || marker

  let map = cache.get(compiler)

  if (!map) {
    map = new Map()
    cache.set(compiler, map)
  }

  let process = map.get(hash)

  if (!process) {
    process = createFormatAwareProcessors(
      bindings,
      coereceMdxTransformOptions(config)
    ).compile
    map.set(hash, process)
  }

  process({ value, path: this.resourcePath }).then(
    (code) => {
      // TODO: no sourcemap
      callback(null, code, null)
    },
    (error) => {
      const fpath = path.relative(this.context, this.resourcePath)
      error.message = `${fpath}:${error.name}: ${error.message}`
      callback(error)
    }
  )
}

function getOptionsHash(options) {
  const hash = createHash('sha256')
  let key

  for (key in options) {
    if (own.call(options, key)) {
      const value = options[key]

      if (value !== undefined) {
        const valueString = JSON.stringify(value)
        hash.update(key + valueString)
      }
    }
  }

  return hash.digest('hex').slice(0, 16)
}

function createFormatAwareProcessors(bindings, compileOptions = {}) {
  const mdExtensions = compileOptions.mdExtensions || md
  const mdxExtensions = compileOptions.mdxExtensions || mdx

  let cachedMarkdown
  let cachedMdx

  return {
    extnames:
      compileOptions.format === 'md'
        ? mdExtensions
        : compileOptions.format === 'mdx'
          ? mdxExtensions
          : mdExtensions.concat(mdxExtensions),
    compile,
  }

  function compile({ value, path: p }) {
    const format =
      compileOptions.format === 'md' || compileOptions.format === 'mdx'
        ? compileOptions.format
        : path.extname(p) &&
            (compileOptions.mdExtensions || md).includes(path.extname(p))
          ? 'md'
          : 'mdx'

    const options = {
      parse: compileOptions.parse,
      development: compileOptions.development,
      providerImportSource: compileOptions.providerImportSource,
      jsx: compileOptions.jsx,
      jsxRuntime: compileOptions.jsxRuntime,
      jsxImportSource: compileOptions.jsxImportSource,
      pragma: compileOptions.pragma,
      pragmaFrag: compileOptions.pragmaFrag,
      pragmaImportSource: compileOptions.pragmaImportSource,
      filepath: p,
    }

    const compileMdx = (input) => bindings.mdx.compile(input, options)

    const processor =
      format === 'md'
        ? cachedMarkdown || (cachedMarkdown = compileMdx)
        : cachedMdx || (cachedMdx = compileMdx)

    return processor(value)
  }
}

module.exports = function (code) {
  const callback = this.async()
  const { loadBindings } = require('next/dist/build/swc')

  loadBindings().then((bindings) => {
    return loader.call(this, code, bindings, callback)
  })
}
Quest for Codev2.0.0
/
SIGN IN