rsnext/packages/next-mdx/mdx-rs-loader.js

171 lines
4.1 KiB
JavaScript

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)
})
}