feat(ui): finalize E-TIB modernization with footer redesign, video optimization, and TS fixes

Former-commit-id: 67ac02c8404cc66893fdf97308574701cca6000c
This commit is contained in:
2026-05-07 10:43:20 +02:00
parent f2fdea96ec
commit 5439df72ea
3712 changed files with 932332 additions and 1867 deletions
@@ -0,0 +1,16 @@
/**
* @typedef {import('hast-util-to-jsx-runtime').Fragment} Fragment
* @typedef {import('hast-util-to-jsx-runtime').Jsx} Jsx
* @typedef {import('hast-util-to-jsx-runtime').JsxDev} JsxDev
* @typedef {import('./lib/util/resolve-evaluate-options.js').UseMdxComponents} UseMdxComponents
* @typedef {import('./lib/compile.js').CompileOptions} CompileOptions
* @typedef {import('./lib/core.js').ProcessorOptions} ProcessorOptions
* @typedef {import('./lib/util/resolve-evaluate-options.js').EvaluateOptions} EvaluateOptions
* @typedef {import('./lib/util/resolve-evaluate-options.js').RunOptions} RunOptions
*/
export {compile, compileSync} from './lib/compile.js'
export {createProcessor} from './lib/core.js'
export {evaluate, evaluateSync} from './lib/evaluate.js'
export {nodeTypes} from './lib/node-types.js'
export {run, runSync} from './lib/run.js'
@@ -0,0 +1,57 @@
/**
* @import {Compatible, VFile} from 'vfile'
* @import {ProcessorOptions} from './core.js'
*/
/**
* @typedef {Omit<ProcessorOptions, 'format'>} CoreProcessorOptions
* Core configuration.
*
* @typedef ExtraOptions
* Extra configuration.
* @property {'detect' | 'md' | 'mdx' | null | undefined} [format='detect']
* Format of `file` (default: `'detect'`).
*
* @typedef {CoreProcessorOptions & ExtraOptions} CompileOptions
* Configuration for `compile`.
*
* `CompileOptions` is the same as `ProcessorOptions` with the exception that
* the `format` option supports a `'detect'` value, which is the default.
* The `'detect'` format means to use `'md'` for files with an extension in
* `mdExtensions` and `'mdx'` otherwise.
*/
import {resolveFileAndOptions} from './util/resolve-file-and-options.js'
import {createProcessor} from './core.js'
/**
* Compile MDX to JS.
*
* @param {Readonly<Compatible>} vfileCompatible
* MDX document to parse.
* @param {Readonly<CompileOptions> | null | undefined} [compileOptions]
* Compile configuration (optional).
* @return {Promise<VFile>}
* Promise to compiled file.
*/
export function compile(vfileCompatible, compileOptions) {
const {file, options} = resolveFileAndOptions(vfileCompatible, compileOptions)
return createProcessor(options).process(file)
}
/**
* Synchronously compile MDX to JS.
*
* When possible please use the async `compile`.
*
* @param {Readonly<Compatible>} vfileCompatible
* MDX document to parse.
* @param {Readonly<CompileOptions> | null | undefined} [compileOptions]
* Compile configuration (optional).
* @return {VFile}
* Compiled file.
*/
export function compileSync(vfileCompatible, compileOptions) {
const {file, options} = resolveFileAndOptions(vfileCompatible, compileOptions)
return createProcessor(options).processSync(file)
}
@@ -0,0 +1,237 @@
/**
* @import {Program} from 'estree-jsx'
* @import {Root} from 'mdast'
* @import {Options as RehypeRecmaOptions} from 'rehype-recma'
* @import {Options as RemarkRehypeOptions} from 'remark-rehype'
* @import {SourceMapGenerator} from 'source-map'
* @import {PluggableList, Processor} from 'unified'
*/
/**
* @typedef ProcessorOptions
* Configuration for `createProcessor`.
* @property {typeof SourceMapGenerator | null | undefined} [SourceMapGenerator]
* Add a source map (object form) as the `map` field on the resulting file
* (optional).
* @property {URL | string | null | undefined} [baseUrl]
* Use this URL as `import.meta.url` and resolve `import` and `export … from`
* relative to it (optional, example: `import.meta.url`).
* @property {boolean | null | undefined} [development=false]
* Whether to add extra info to error messages in generated code and use the
* development automatic JSX runtime (`Fragment` and `jsxDEV` from
* `/jsx-dev-runtime`) (default: `false`);
* when using the webpack loader (`@mdx-js/loader`) or the Rollup integration
* (`@mdx-js/rollup`) through Vite, this is automatically inferred from how
* you configure those tools.
* @property {RehypeRecmaOptions['elementAttributeNameCase']} [elementAttributeNameCase='react']
* Casing to use for attribute names (default: `'react'`);
* HTML casing is for example `class`, `stroke-linecap`, `xml:lang`;
* React casing is for example `className`, `strokeLinecap`, `xmlLang`;
* for JSX components written in MDX, the author has to be aware of which
* framework they use and write code accordingly;
* for AST nodes generated by this project, this option configures it
* @property {'md' | 'mdx' | null | undefined} [format='mdx']
* format of the file (default: `'mdx'`);
* `'md'` means treat as markdown and `'mdx'` means treat as MDX.
* @property {boolean | null | undefined} [jsx=false]
* Whether to keep JSX (default: `false`);
* the default is to compile JSX away so that the resulting file is
* immediately runnable.
* @property {string | null | undefined} [jsxImportSource='react']
* Place to import automatic JSX runtimes from (default: `'react'`);
* when in the `automatic` runtime, this is used to define an import for
* `Fragment`, `jsx`, `jsxDEV`, and `jsxs`.
* @property {'automatic' | 'classic' | null | undefined} [jsxRuntime='automatic']
* JSX runtime to use (default: `'automatic'`);
* the automatic runtime compiles to `import _jsx from
* '$importSource/jsx-runtime'\n_jsx('p')`;
* the classic runtime compiles to calls such as `h('p')`.
*
* > 👉 **Note**: support for the classic runtime is deprecated and will
* > likely be removed in the next major version.
* @property {ReadonlyArray<string> | null | undefined} [mdExtensions]
* List of markdown extensions, with dot (default: `['.md', '.markdown', …]`);
* affects integrations.
* @property {ReadonlyArray<string> | null | undefined} [mdxExtensions]
* List of MDX extensions, with dot (default: `['.mdx']`);
* affects integrations.
* @property {'function-body' | 'program' | null | undefined} [outputFormat='program']
* Output format to generate (default: `'program'`);
* in most cases `'program'` should be used, it results in a whole program;
* internally `evaluate` uses `'function-body'` to compile to
* code that can be passed to `run`;
* in some cases, you might want what `evaluate` does in separate steps, such
* as when compiling on the server and running on the client.
* @property {string | null | undefined} [pragma='React.createElement']
* Pragma for JSX, used in the classic runtime as an identifier for function
* calls: `<x />` to `React.createElement('x')` (default:
* `'React.createElement'`);
* when changing this, you should also define `pragmaFrag` and
* `pragmaImportSource` too.
*
* > 👉 **Note**: support for the classic runtime is deprecated and will
* > likely be removed in the next major version.
* @property {string | null | undefined} [pragmaFrag='React.Fragment']
* Pragma for fragment symbol, used in the classic runtime as an identifier
* for unnamed calls: `<>` to `React.createElement(React.Fragment)` (default:
* `'React.Fragment'`);
* when changing this, you should also define `pragma` and
* `pragmaImportSource` too.
*
* > 👉 **Note**: support for the classic runtime is deprecated and will
* > likely be removed in the next major version.
* @property {string | null | undefined} [pragmaImportSource='react']
* Where to import the identifier of `pragma` from, used in the classic
* runtime (default: `'react'`);
* to illustrate, when `pragma` is `'a.b'` and `pragmaImportSource` is `'c'`
* the following will be generated: `import a from 'c'` and things such as
* `a.b('h1', {})`.
* when changing this, you should also define `pragma` and `pragmaFrag` too.
*
* > 👉 **Note**: support for the classic runtime is deprecated and will
* > likely be removed in the next major version.
* @property {string | null | undefined} [providerImportSource]
* Place to import a provider from (optional, example: `'@mdx-js/react'`);
* normally it’s used for runtimes that support context (React, Preact), but
* it can be used to inject components into the compiled code;
* the module must export and identifier `useMDXComponents` which is called
* without arguments to get an object of components (`MDXComponents` from
* `mdx/types.js`).
* @property {PluggableList | null | undefined} [recmaPlugins]
* List of recma plugins (optional).
* @property {PluggableList | null | undefined} [remarkPlugins]
* List of remark plugins (optional).
* @property {PluggableList | null | undefined} [rehypePlugins]
* List of rehype plugins (optional).
* @property {Readonly<RemarkRehypeOptions> | null | undefined} [remarkRehypeOptions]
* Options to pass to `remark-rehype` (optional);
* in particular, you might want to pass configuration for footnotes if your
* content is not in English;
* the option `allowDangerousHtml` will always be set to `true` and the MDX
* nodes (see `nodeTypes`) are passed through.
* @property {RehypeRecmaOptions['stylePropertyNameCase']} [stylePropertyNameCase='dom']
* Casing to use for property names in `style` objects (default: `'dom'`);
* CSS casing is for example `background-color` and `-webkit-line-clamp`;
* DOM casing is for example `backgroundColor` and `WebkitLineClamp`;
* for JSX components written in MDX, the author has to be aware of which
* framework they use and write code accordingly;
* for AST nodes generated by this project, this option configures it
* @property {boolean | null | undefined} [tableCellAlignToStyle=true]
* Turn obsolete `align` properties on `td` and `th` into CSS `style`
* properties (default: `true`).
*/
import {unreachable} from 'devlop'
import recmaBuildJsx from 'recma-build-jsx'
import recmaJsx from 'recma-jsx'
import recmaStringify from 'recma-stringify'
import rehypeRecma from 'rehype-recma'
import remarkMdx from 'remark-mdx'
import remarkParse from 'remark-parse'
import remarkRehype from 'remark-rehype'
import {unified} from 'unified'
import {recmaBuildJsxTransform} from './plugin/recma-build-jsx-transform.js'
import {recmaDocument} from './plugin/recma-document.js'
import {recmaJsxRewrite} from './plugin/recma-jsx-rewrite.js'
import {rehypeRemoveRaw} from './plugin/rehype-remove-raw.js'
import {remarkMarkAndUnravel} from './plugin/remark-mark-and-unravel.js'
import {nodeTypes} from './node-types.js'
const removedOptions = [
'compilers',
'filepath',
'hastPlugins',
'mdPlugins',
'skipExport',
'wrapExport'
]
let warned = false
/**
* Create a processor to compile markdown or MDX to JavaScript.
*
* > **Note**: `format: 'detect'` is not allowed in `ProcessorOptions`.
*
* @param {Readonly<ProcessorOptions> | null | undefined} [options]
* Configuration (optional).
* @return {Processor<Root, Program, Program, Program, string>}
* Processor.
*/
export function createProcessor(options) {
const settings = options || {}
let index = -1
while (++index < removedOptions.length) {
const key = removedOptions[index]
if (key in settings) {
unreachable(
'Unexpected removed option `' +
key +
'`; see <https://mdxjs.com/migrating/v2/> on how to migrate'
)
}
}
// @ts-expect-error: throw an error for a runtime value which is not allowed
// by the types.
if (settings.format === 'detect') {
unreachable(
"Unexpected `format: 'detect'`, which is not supported by `createProcessor`, expected `'mdx'` or `'md'`"
)
}
if (
(settings.jsxRuntime === 'classic' ||
settings.pragma ||
settings.pragmaFrag ||
settings.pragmaImportSource) &&
!warned
) {
warned = true
console.warn(
"Unexpected deprecated option `jsxRuntime: 'classic'`, `pragma`, `pragmaFrag`, or `pragmaImportSource`; see <https://mdxjs.com/migrating/v3/> on how to migrate"
)
}
const pipeline = unified().use(remarkParse)
if (settings.format !== 'md') {
pipeline.use(remarkMdx)
}
const remarkRehypeOptions = settings.remarkRehypeOptions || {}
pipeline
.use(remarkMarkAndUnravel)
.use(settings.remarkPlugins || [])
.use(remarkRehype, {
...remarkRehypeOptions,
allowDangerousHtml: true,
passThrough: [...(remarkRehypeOptions.passThrough || []), ...nodeTypes]
})
.use(settings.rehypePlugins || [])
if (settings.format === 'md') {
pipeline.use(rehypeRemoveRaw)
}
pipeline
// @ts-expect-error: `Program` is close enough to a `Node`,
// but type inference has trouble with it and bridges.
.use(rehypeRecma, settings)
.use(recmaDocument, settings)
.use(recmaJsxRewrite, settings)
if (!settings.jsx) {
pipeline.use(recmaBuildJsx, settings).use(recmaBuildJsxTransform, settings)
}
pipeline
.use(recmaJsx)
.use(recmaStringify, settings)
.use(settings.recmaPlugins || [])
// @ts-expect-error: TS doesn’t get the plugins we added with if-statements.
return pipeline
}
@@ -0,0 +1,69 @@
/**
* @import {MDXModule} from 'mdx/types.js'
* @import {Compatible} from 'vfile'
* @import {EvaluateOptions} from './util/resolve-evaluate-options.js'
*/
import {resolveEvaluateOptions} from './util/resolve-evaluate-options.js'
import {compile, compileSync} from './compile.js'
import {run, runSync} from './run.js'
/**
* Compile and run MDX.
*
* When you trust your content, `evaluate` can work.
* When possible, use `compile`, write to a file, and then run with Node or use
* one of the integrations.
*
* > ☢️ **Danger**: it’s called **evaluate** because it `eval`s JavaScript.
*
* ###### Notes
*
* Compiling (and running) MDX takes time.
*
* If you are live-rendering a string of MDX that often changes using a virtual
* DOM based framework (such as React), one performance improvement is to call
* the `MDXContent` component yourself.
* The reason is that the `evaluate` creates a new function each time, which
* cannot be diffed:
*
* ```diff
* const {default: MDXContent} = await evaluate('…')
*
* -<MDXContent {...props} />
* +MDXContent(props)
* ```
*
* @param {Readonly<Compatible>} file
* MDX document to parse.
* @param {Readonly<EvaluateOptions>} options
* Configuration (**required**).
* @return {Promise<MDXModule>}
* Promise to a module;
* the result is an object with a `default` field set to the component;
* anything else that was exported is available too.
*/
export async function evaluate(file, options) {
const {compiletime, runtime} = resolveEvaluateOptions(options)
return run(await compile(file, compiletime), runtime)
}
/**
* Compile and run MDX, synchronously.
*
* When possible please use the async `evaluate`.
*
* > ☢️ **Danger**: it’s called **evaluate** because it `eval`s JavaScript.
*
* @param {Readonly<Compatible>} file
* MDX document to parse.
* @param {Readonly<EvaluateOptions>} options
* Configuration (**required**).
* @return {MDXModule}
* Module.
*/
export function evaluateSync(file, options) {
const {compiletime, runtime} = resolveEvaluateOptions(options)
return runSync(compileSync(file, compiletime), runtime)
}
@@ -0,0 +1,11 @@
/**
* List of node types made by `mdast-util-mdx`, which have to be passed
* through untouched from the mdast tree to the hast tree.
*/
export const nodeTypes = /** @type {const} */ ([
'mdxFlowExpression',
'mdxJsxFlowElement',
'mdxJsxTextElement',
'mdxTextExpression',
'mdxjsEsm'
])
@@ -0,0 +1,80 @@
/**
* @import {Program} from 'estree-jsx'
*/
/**
* @typedef Options
* Configuration for internal plugin `recma-build-jsx-transform`.
* @property {'function-body' | 'program' | null | undefined} [outputFormat='program']
* Whether to keep the import of the automatic runtime or get it from
* `arguments[0]` instead (default: `'program'`).
*/
import {specifiersToDeclarations} from '../util/estree-util-specifiers-to-declarations.js'
import {toIdOrMemberExpression} from '../util/estree-util-to-id-or-member-expression.js'
/**
* Plugin to change the tree after compiling JSX away.
*
* @param {Readonly<Options> | null | undefined} [options]
* Configuration (optional).
* @returns
* Transform.
*/
export function recmaBuildJsxTransform(options) {
/* c8 ignore next -- always given in `@mdx-js/mdx` */
const {outputFormat} = options || {}
/**
* @param {Program} tree
* Tree.
* @returns {undefined}
* Nothing.
*/
return function (tree) {
// Remove the pragma comment that we injected ourselves as it is no longer
// needed.
if (tree.comments) {
tree.comments = tree.comments.filter(function (d) {
return !d.data?._mdxIsPragmaComment
})
}
// When compiling to a function body, replace the import that was just
// generated, and get `jsx`, `jsxs`, and `Fragment` from `arguments[0]`
// instead.
if (outputFormat === 'function-body') {
let index = 0
// Skip directives: JS currently only has `use strict`, but Acorn allows
// arbitrary ones.
// Practically things like `use client` could be used?
while (index < tree.body.length) {
const child = tree.body[index]
if ('directive' in child && child.directive) {
index++
} else {
break
}
}
const declaration = tree.body[index]
if (
declaration &&
declaration.type === 'ImportDeclaration' &&
typeof declaration.source.value === 'string' &&
/\/jsx-(dev-)?runtime$/.test(declaration.source.value)
) {
tree.body[index] = {
type: 'VariableDeclaration',
kind: 'const',
declarations: specifiersToDeclarations(
declaration.specifiers,
toIdOrMemberExpression(['arguments', 0])
)
}
}
}
}
}
@@ -0,0 +1,915 @@
/**
* @import {
CallExpression,
Directive,
ExportAllDeclaration,
ExportDefaultDeclaration,
ExportNamedDeclaration,
ExportSpecifier,
Expression,
FunctionDeclaration,
Identifier,
ImportDeclaration,
ImportDefaultSpecifier,
ImportExpression,
ImportSpecifier,
JSXElement,
JSXFragment,
Literal,
ModuleDeclaration,
Node,
Program,
Property,
SimpleLiteral,
SpreadElement,
Statement,
VariableDeclarator
* } from 'estree-jsx'
* @import {VFile} from 'vfile'
* @import {ProcessorOptions} from '../core.js'
*/
import {ok as assert} from 'devlop'
import {createVisitors} from 'estree-util-scope'
import {walk} from 'estree-walker'
import {positionFromEstree} from 'unist-util-position-from-estree'
import {stringifyPosition} from 'unist-util-stringify-position'
import {create} from '../util/estree-util-create.js'
import {declarationToExpression} from '../util/estree-util-declaration-to-expression.js'
import {isDeclaration} from '../util/estree-util-is-declaration.js'
import {specifiersToDeclarations} from '../util/estree-util-specifiers-to-declarations.js'
import {toIdOrMemberExpression} from '../util/estree-util-to-id-or-member-expression.js'
/**
* Wrap the estree in `MDXContent`.
*
* @param {Readonly<ProcessorOptions>} options
* Configuration.
* @returns
* Transform.
*/
export function recmaDocument(options) {
const baseUrl = options.baseUrl || undefined
const baseHref = typeof baseUrl === 'object' ? baseUrl.href : baseUrl
const outputFormat = options.outputFormat || 'program'
const pragma =
options.pragma === undefined ? 'React.createElement' : options.pragma
const pragmaFrag =
options.pragmaFrag === undefined ? 'React.Fragment' : options.pragmaFrag
const pragmaImportSource = options.pragmaImportSource || 'react'
const jsxImportSource = options.jsxImportSource || 'react'
const jsxRuntime = options.jsxRuntime || 'automatic'
/**
* @param {Program} tree
* Tree.
* @param {VFile} file
* File.
* @returns {undefined}
* Nothing.
*/
return function (tree, file) {
/** @type {Array<[string, string] | string>} */
const exportedValues = []
/** @type {Array<Directive | ModuleDeclaration | Statement>} */
const replacement = []
let exportAllCount = 0
/** @type {ExportDefaultDeclaration | ExportSpecifier | undefined} */
let layout
/** @type {boolean | undefined} */
let content
/** @type {Node} */
let child
if (jsxRuntime === 'classic' && pragmaFrag) {
injectPragma(tree, '@jsxFrag', pragmaFrag)
}
if (jsxRuntime === 'classic' && pragma) {
injectPragma(tree, '@jsx', pragma)
}
if (jsxRuntime === 'automatic' && jsxImportSource) {
injectPragma(tree, '@jsxImportSource', jsxImportSource)
}
if (jsxRuntime) {
injectPragma(tree, '@jsxRuntime', jsxRuntime)
}
if (jsxRuntime === 'classic' && pragmaImportSource) {
if (!pragma) {
throw new Error(
'Missing `pragma` in classic runtime with `pragmaImportSource`'
)
}
handleEsm({
type: 'ImportDeclaration',
specifiers: [
{
type: 'ImportDefaultSpecifier',
local: {type: 'Identifier', name: pragma.split('.')[0]}
}
],
attributes: [],
source: {type: 'Literal', value: pragmaImportSource}
})
}
// Find the `export default`, the JSX expression, and leave the rest
// (import/exports) as they are.
for (child of tree.body) {
// ```tsx
// export default properties => <>{properties.children}</>
// ```
//
// Treat it as an inline layout declaration.
if (child.type === 'ExportDefaultDeclaration') {
if (layout) {
file.fail(
'Unexpected duplicate layout, expected a single layout (previous: ' +
stringifyPosition(positionFromEstree(layout)) +
')',
{
ancestors: [tree, child],
place: positionFromEstree(child),
ruleId: 'duplicate-layout',
source: 'recma-document'
}
)
}
layout = child
replacement.push({
type: 'VariableDeclaration',
kind: 'const',
declarations: [
{
type: 'VariableDeclarator',
id: {type: 'Identifier', name: 'MDXLayout'},
init: isDeclaration(child.declaration)
? declarationToExpression(child.declaration)
: child.declaration
}
]
})
}
// ```tsx
// export {a, b as c} from 'd'
// ```
else if (child.type === 'ExportNamedDeclaration' && child.source) {
// Cast because always simple.
const source = /** @type {SimpleLiteral} */ (child.source)
// Remove `default` or `as default`, but not `default as`, specifier.
child.specifiers = child.specifiers.filter(function (specifier) {
if (
specifier.exported.type === 'Identifier' &&
specifier.exported.name === 'default'
) {
if (layout) {
file.fail(
'Unexpected duplicate layout, expected a single layout (previous: ' +
stringifyPosition(positionFromEstree(layout)) +
')',
{
ancestors: [tree, child, specifier],
place: positionFromEstree(child),
ruleId: 'duplicate-layout',
source: 'recma-document'
}
)
}
layout = specifier
// Make it just an import: `import MDXLayout from '…'`.
/** @type {Array<ImportDefaultSpecifier | ImportSpecifier>} */
const specifiers = []
// Default as default / something else as default.
if (
specifier.local.type === 'Identifier' &&
specifier.local.name === 'default'
) {
specifiers.push({
type: 'ImportDefaultSpecifier',
local: {type: 'Identifier', name: 'MDXLayout'}
})
} else {
/** @type {ImportSpecifier} */
const importSpecifier = {
type: 'ImportSpecifier',
imported: specifier.local,
local: {type: 'Identifier', name: 'MDXLayout'}
}
create(specifier.local, importSpecifier)
specifiers.push(importSpecifier)
}
/** @type {Literal} */
const from = {type: 'Literal', value: source.value}
create(source, from)
/** @type {ImportDeclaration} */
const declaration = {
type: 'ImportDeclaration',
specifiers,
attributes: [],
source: from
}
create(specifier, declaration)
handleEsm(declaration)
return false
}
return true
})
// If there are other things imported, keep it.
if (child.specifiers.length > 0) {
handleExport(child)
}
}
// ```tsx
// export {a, b as c}
// export * from 'a'
// ```
else if (
child.type === 'ExportNamedDeclaration' ||
child.type === 'ExportAllDeclaration'
) {
handleExport(child)
} else if (child.type === 'ImportDeclaration') {
handleEsm(child)
} else if (
child.type === 'ExpressionStatement' &&
(child.expression.type === 'JSXElement' ||
child.expression.type === 'JSXFragment')
) {
content = true
replacement.push(
...createMdxContent(child.expression, outputFormat, Boolean(layout))
)
} else {
// This catch-all branch is because plugins might add other things.
// Normally, we only have import/export/jsx, but just add whatever’s
// there.
replacement.push(child)
}
}
// If there was no JSX content at all, add an empty function.
if (!content) {
replacement.push(
...createMdxContent(undefined, outputFormat, Boolean(layout))
)
}
exportedValues.push(['MDXContent', 'default'])
if (outputFormat === 'function-body') {
replacement.push({
type: 'ReturnStatement',
argument: {
type: 'ObjectExpression',
properties: [
...Array.from({length: exportAllCount}).map(
/**
* @param {undefined} _
* Nothing.
* @param {number} index
* Index.
* @returns {SpreadElement}
* Node.
*/
function (_, index) {
return {
type: 'SpreadElement',
argument: {
type: 'Identifier',
name: '_exportAll' + (index + 1)
}
}
}
),
...exportedValues.map(function (d) {
/** @type {Property} */
const property = {
type: 'Property',
kind: 'init',
method: false,
computed: false,
shorthand: typeof d === 'string',
key: {
type: 'Identifier',
name: typeof d === 'string' ? d : d[1]
},
value: {
type: 'Identifier',
name: typeof d === 'string' ? d : d[0]
}
}
return property
})
]
}
})
}
tree.body = replacement
let usesImportMetaUrlVariable = false
let usesResolveDynamicHelper = false
if (baseHref || outputFormat === 'function-body') {
walk(tree, {
enter(node) {
if (
(node.type === 'ExportAllDeclaration' ||
node.type === 'ExportNamedDeclaration' ||
node.type === 'ImportDeclaration') &&
node.source
) {
// We never hit this branch when generating function bodies, as
// statements are already compiled away into import expressions.
assert(baseHref, 'unexpected missing `baseHref` in branch')
let value = node.source.value
// The literal source for statements can only be string.
assert(typeof value === 'string', 'expected string source')
// Resolve a specifier.
// This is the same as `_resolveDynamicMdxSpecifier`, which has to
// be injected to work with expressions at runtime, but as we have
// `baseHref` at compile time here and statements are static
// strings, we can do it now.
try {
// To do: next major: use `URL.canParse`.
// eslint-disable-next-line no-new
new URL(value)
// Fine: a full URL.
} catch {
if (
value.startsWith('/') ||
value.startsWith('./') ||
value.startsWith('../')
) {
value = new URL(value, baseHref).href
} else {
// Fine: are bare specifier.
}
}
/** @type {SimpleLiteral} */
const replacement = {type: 'Literal', value}
create(node.source, replacement)
node.source = replacement
return
}
if (node.type === 'ImportExpression') {
usesResolveDynamicHelper = true
/** @type {CallExpression} */
const replacement = {
type: 'CallExpression',
callee: {type: 'Identifier', name: '_resolveDynamicMdxSpecifier'},
arguments: [node.source],
optional: false
}
node.source = replacement
return
}
// To do: add support for `import.meta.resolve`.
if (
node.type === 'MemberExpression' &&
'object' in node &&
node.object.type === 'MetaProperty' &&
node.property.type === 'Identifier' &&
node.object.meta.name === 'import' &&
node.object.property.name === 'meta' &&
node.property.name === 'url'
) {
usesImportMetaUrlVariable = true
/** @type {Identifier} */
const replacement = {type: 'Identifier', name: '_importMetaUrl'}
create(node, replacement)
this.replace(replacement)
}
}
})
}
if (usesResolveDynamicHelper) {
if (!baseHref) {
usesImportMetaUrlVariable = true
}
tree.body.push(
resolveDynamicMdxSpecifier(
baseHref
? {type: 'Literal', value: baseHref}
: {type: 'Identifier', name: '_importMetaUrl'}
)
)
}
if (usesImportMetaUrlVariable) {
assert(
outputFormat === 'function-body',
'expected `function-body` when using dynamic url injection'
)
tree.body.unshift(...createImportMetaUrlVariable())
}
/**
* @param {ExportAllDeclaration | ExportNamedDeclaration} node
* Export node.
* @returns {undefined}
* Nothing.
*/
function handleExport(node) {
if (node.type === 'ExportNamedDeclaration') {
// ```tsx
// export function a() {}
// export class A {}
// export var a = 1
// ```
if (node.declaration) {
const visitors = createVisitors()
// Walk the top-level scope.
walk(node, {
enter(node) {
visitors.enter(node)
if (
node.type === 'ArrowFunctionExpression' ||
node.type === 'FunctionDeclaration' ||
node.type === 'FunctionExpression'
) {
this.skip()
visitors.exit(node)
}
},
leave: visitors.exit
})
exportedValues.push(...visitors.scopes[0].defined)
}
// ```tsx
// export {a, b as c}
// export {a, b as c} from 'd'
// ```
for (child of node.specifiers) {
if (child.exported.type === 'Identifier') {
exportedValues.push(child.exported.name)
/* c8 ignore next 5 -- to do: <https://github.com/mdx-js/mdx/issues/2536> */
} else {
// Must be string.
assert(typeof child.exported.value === 'string')
exportedValues.push(child.exported.value)
}
}
}
handleEsm(node)
}
/**
* @param {ExportAllDeclaration | ExportNamedDeclaration | ImportDeclaration} node
* Export or import node.
* @returns {undefined}
* Nothing.
*/
function handleEsm(node) {
/** @type {ModuleDeclaration | Statement | undefined} */
let replace
/** @type {Expression} */
let init
if (outputFormat === 'function-body') {
if (
// Always have a source:
node.type === 'ImportDeclaration' ||
node.type === 'ExportAllDeclaration' ||
// Source optional:
(node.type === 'ExportNamedDeclaration' && node.source)
) {
// We always have a source, but types say they can be missing.
assert(node.source, 'expected `node.source` to be defined')
// ```
// import 'a'
// //=> await import('a')
// import a from 'b'
// //=> const {default: a} = await import('b')
// export {a, b as c} from 'd'
// //=> const {a, c: b} = await import('d')
// export * from 'a'
// //=> const _exportAll0 = await import('a')
// ```
/** @type {ImportExpression} */
const argument = {type: 'ImportExpression', source: node.source}
create(node, argument)
init = {type: 'AwaitExpression', argument}
if (
(node.type === 'ImportDeclaration' ||
node.type === 'ExportNamedDeclaration') &&
node.specifiers.length === 0
) {
replace = {type: 'ExpressionStatement', expression: init}
} else {
replace = {
type: 'VariableDeclaration',
kind: 'const',
declarations:
node.type === 'ExportAllDeclaration'
? [
{
type: 'VariableDeclarator',
id: {
type: 'Identifier',
name: '_exportAll' + ++exportAllCount
},
init
}
]
: specifiersToDeclarations(node.specifiers, init)
}
}
} else if (node.declaration) {
replace = node.declaration
} else {
/** @type {Array<VariableDeclarator>} */
const declarators = []
for (const specifier of node.specifiers) {
// `id` can only be an identifier,
// so we ignore literal.
if (
specifier.exported.type === 'Identifier' &&
specifier.local.type === 'Identifier' &&
specifier.local.name !== specifier.exported.name
) {
declarators.push({
type: 'VariableDeclarator',
id: specifier.exported,
init: specifier.local
})
}
}
if (declarators.length > 0) {
replace = {
type: 'VariableDeclaration',
kind: 'const',
declarations: declarators
}
}
}
} else {
replace = node
}
if (replace) {
replacement.push(replace)
}
}
}
/**
* @param {Readonly<Expression> | undefined} content
* Content.
* @param {'function-body' | 'program'} outputFormat
* Output format.
* @param {boolean | undefined} [hasInternalLayout=false]
* Whether there’s an internal layout (default: `false`).
* @returns {Array<ExportDefaultDeclaration | FunctionDeclaration>}
* Functions.
*/
function createMdxContent(content, outputFormat, hasInternalLayout) {
/** @type {JSXElement} */
const element = {
type: 'JSXElement',
openingElement: {
type: 'JSXOpeningElement',
name: {type: 'JSXIdentifier', name: 'MDXLayout'},
attributes: [
{
type: 'JSXSpreadAttribute',
argument: {type: 'Identifier', name: 'props'}
}
],
selfClosing: false
},
closingElement: {
type: 'JSXClosingElement',
name: {type: 'JSXIdentifier', name: 'MDXLayout'}
},
children: [
{
type: 'JSXElement',
openingElement: {
type: 'JSXOpeningElement',
name: {type: 'JSXIdentifier', name: '_createMdxContent'},
attributes: [
{
type: 'JSXSpreadAttribute',
argument: {type: 'Identifier', name: 'props'}
}
],
selfClosing: true
},
closingElement: null,
children: []
}
]
}
let result = /** @type {Expression} */ (element)
if (!hasInternalLayout) {
result = {
type: 'ConditionalExpression',
test: {type: 'Identifier', name: 'MDXLayout'},
consequent: result,
alternate: {
type: 'CallExpression',
callee: {type: 'Identifier', name: '_createMdxContent'},
arguments: [{type: 'Identifier', name: 'props'}],
optional: false
}
}
}
let argument =
// Cast because TS otherwise does not think `JSXFragment`s are expressions.
/** @type {Readonly<Expression> | Readonly<JSXFragment>} */ (
content || {type: 'Identifier', name: 'undefined'}
)
// Unwrap a fragment of a single element.
if (
argument.type === 'JSXFragment' &&
argument.children.length === 1 &&
argument.children[0].type === 'JSXElement'
) {
argument = argument.children[0]
}
let awaitExpression = false
walk(argument, {
enter(node) {
if (
node.type === 'ArrowFunctionExpression' ||
node.type === 'FunctionDeclaration' ||
node.type === 'FunctionExpression'
) {
return this.skip()
}
if (
node.type === 'AwaitExpression' ||
/* c8 ignore next 2 -- can only occur in a function (which then can
* only be async, so skipped it) */
(node.type === 'ForOfStatement' && node.await)
) {
awaitExpression = true
}
}
})
/** @type {FunctionDeclaration} */
const declaration = {
type: 'FunctionDeclaration',
id: {type: 'Identifier', name: 'MDXContent'},
params: [
{
type: 'AssignmentPattern',
left: {type: 'Identifier', name: 'props'},
right: {type: 'ObjectExpression', properties: []}
}
],
body: {
type: 'BlockStatement',
body: [{type: 'ReturnStatement', argument: result}]
}
}
return [
{
type: 'FunctionDeclaration',
async: awaitExpression,
id: {type: 'Identifier', name: '_createMdxContent'},
params: [{type: 'Identifier', name: 'props'}],
body: {
type: 'BlockStatement',
body: [
{
type: 'ReturnStatement',
// Cast because TS doesn’t think `JSXFragment` is an expression.
// eslint-disable-next-line object-shorthand
argument: /** @type {Expression} */ (argument)
}
]
}
},
outputFormat === 'program'
? {type: 'ExportDefaultDeclaration', declaration}
: declaration
]
}
}
/**
* @param {Program} tree
* @param {string} name
* @param {string} value
* @returns {undefined}
*/
function injectPragma(tree, name, value) {
tree.comments?.unshift({
type: 'Block',
value: name + ' ' + value,
data: {_mdxIsPragmaComment: true}
})
}
/**
* @param {Expression} importMetaUrl
* @returns {FunctionDeclaration}
*/
function resolveDynamicMdxSpecifier(importMetaUrl) {
return {
type: 'FunctionDeclaration',
id: {type: 'Identifier', name: '_resolveDynamicMdxSpecifier'},
generator: false,
async: false,
params: [{type: 'Identifier', name: 'd'}],
body: {
type: 'BlockStatement',
body: [
{
type: 'IfStatement',
test: {
type: 'BinaryExpression',
left: {
type: 'UnaryExpression',
operator: 'typeof',
prefix: true,
argument: {type: 'Identifier', name: 'd'}
},
operator: '!==',
right: {type: 'Literal', value: 'string'}
},
consequent: {
type: 'ReturnStatement',
argument: {type: 'Identifier', name: 'd'}
},
alternate: null
},
// To do: use `URL.canParse` when widely supported (see commented
// out code below).
{
type: 'TryStatement',
block: {
type: 'BlockStatement',
body: [
{
type: 'ExpressionStatement',
expression: {
type: 'NewExpression',
callee: {type: 'Identifier', name: 'URL'},
arguments: [{type: 'Identifier', name: 'd'}]
}
},
{
type: 'ReturnStatement',
argument: {type: 'Identifier', name: 'd'}
}
]
},
handler: {
type: 'CatchClause',
param: null,
body: {type: 'BlockStatement', body: []}
},
finalizer: null
},
// To do: use `URL.canParse` when widely supported.
// {
// type: 'IfStatement',
// test: {
// type: 'CallExpression',
// callee: toIdOrMemberExpression(['URL', 'canParse']),
// arguments: [{type: 'Identifier', name: 'd'}],
// optional: false
// },
// consequent: {
// type: 'ReturnStatement',
// argument: {type: 'Identifier', name: 'd'}
// },
// alternate: null
// },
{
type: 'IfStatement',
test: {
type: 'LogicalExpression',
left: {
type: 'LogicalExpression',
left: {
type: 'CallExpression',
callee: toIdOrMemberExpression(['d', 'startsWith']),
arguments: [{type: 'Literal', value: '/'}],
optional: false
},
operator: '||',
right: {
type: 'CallExpression',
callee: toIdOrMemberExpression(['d', 'startsWith']),
arguments: [{type: 'Literal', value: './'}],
optional: false
}
},
operator: '||',
right: {
type: 'CallExpression',
callee: toIdOrMemberExpression(['d', 'startsWith']),
arguments: [{type: 'Literal', value: '../'}],
optional: false
}
},
consequent: {
type: 'ReturnStatement',
argument: {
type: 'MemberExpression',
object: {
type: 'NewExpression',
callee: {type: 'Identifier', name: 'URL'},
arguments: [{type: 'Identifier', name: 'd'}, importMetaUrl]
},
property: {type: 'Identifier', name: 'href'},
computed: false,
optional: false
}
},
alternate: null
},
{
type: 'ReturnStatement',
argument: {type: 'Identifier', name: 'd'}
}
]
}
}
}
/**
* @returns {Array<Statement>}
*/
function createImportMetaUrlVariable() {
return [
{
type: 'VariableDeclaration',
declarations: [
{
type: 'VariableDeclarator',
id: {type: 'Identifier', name: '_importMetaUrl'},
init: toIdOrMemberExpression(['arguments', 0, 'baseUrl'])
}
],
kind: 'const'
},
{
type: 'IfStatement',
test: {
type: 'UnaryExpression',
operator: '!',
prefix: true,
argument: {type: 'Identifier', name: '_importMetaUrl'}
},
consequent: {
type: 'ThrowStatement',
argument: {
type: 'NewExpression',
callee: {type: 'Identifier', name: 'Error'},
arguments: [
{
type: 'Literal',
value:
'Unexpected missing `options.baseUrl` needed to support `export … from`, `import`, or `import.meta.url` when generating `function-body`'
}
]
}
},
alternate: null
}
]
}
@@ -0,0 +1,620 @@
/**
* @import {
Expression,
Function as EstreeFunction,
Identifier,
ImportSpecifier,
JSXElement,
ModuleDeclaration,
ObjectPattern,
Program,
Property,
SpreadElement,
Statement,
VariableDeclarator
* } from 'estree-jsx'
* @import {Scope} from 'estree-util-scope'
* @import {VFile} from 'vfile'
* @import {ProcessorOptions} from '../core.js'
*/
/**
* @typedef StackEntry
* Entry.
* @property {Array<string>} components
* Used components.
* @property {Map<string, string>} idToInvalidComponentName
* Map of JSX identifiers which cannot be used as JS identifiers, to valid JS identifiers.
* @property {Readonly<EstreeFunction>} node
* Function.
* @property {Array<string>} objects
* Identifiers of used objects (such as `x` in `x.y`).
* @property {Record<string, {node: Readonly<JSXElement>, component: boolean}>} references
* Map of JSX identifiers for components and objects, to where they were first used.
* @property {Array<string>} tags
* Tag names.
*/
import {name as isIdentifierName} from 'estree-util-is-identifier-name'
import {createVisitors} from 'estree-util-scope'
import {walk} from 'estree-walker'
import {stringifyPosition} from 'unist-util-stringify-position'
import {positionFromEstree} from 'unist-util-position-from-estree'
import {specifiersToDeclarations} from '../util/estree-util-specifiers-to-declarations.js'
import {toBinaryAddition} from '../util/estree-util-to-binary-addition.js'
import {
toIdOrMemberExpression,
toJsxIdOrMemberExpression
} from '../util/estree-util-to-id-or-member-expression.js'
/**
* A plugin that rewrites JSX in functions to accept components as
* `props.components` (when the function is called `_createMdxContent`), or from
* a provider (if there is one).
* It also makes sure that any undefined components are defined: either from
* received components or as a function that throws an error.
*
* @param {Readonly<ProcessorOptions>} options
* Configuration (optional).
* @returns
* Transform.
*/
export function recmaJsxRewrite(options) {
const {development, outputFormat, providerImportSource} = options
/**
* @param {Program} tree
* Tree.
* @param {VFile} file
* File.
* @returns {undefined}
* Nothing.
*/
return function (tree, file) {
const visitors = createVisitors()
/** @type {Array<StackEntry>} */
const functionStack = []
let importProvider = false
let createErrorHelper = false
walk(tree, {
enter(node) {
visitors.enter(node)
if (
node.type === 'FunctionDeclaration' ||
node.type === 'FunctionExpression' ||
node.type === 'ArrowFunctionExpression'
) {
functionStack.push({
components: [],
idToInvalidComponentName: new Map(),
node,
objects: [],
references: {},
tags: []
})
// `MDXContent` only ever contains `MDXLayout`.
if (
isNamedFunction(node, 'MDXContent') &&
!inScope(visitors.scopes, 'MDXLayout')
) {
functionStack[0].components.push('MDXLayout')
}
}
const functionInfo = functionStack[0]
if (
!functionInfo ||
(!isNamedFunction(functionInfo.node, '_createMdxContent') &&
!providerImportSource)
) {
return
}
if (node.type === 'JSXElement') {
let name = node.openingElement.name
// `<x.y>`, `<Foo.Bar>`, `<x.y.z>`.
if (name.type === 'JSXMemberExpression') {
/** @type {Array<string>} */
const ids = []
// Find the left-most identifier.
while (name.type === 'JSXMemberExpression') {
ids.unshift(name.property.name)
name = name.object
}
ids.unshift(name.name)
const fullId = ids.join('.')
const id = name.name
const isInScope = inScope(visitors.scopes, id)
if (
!Object.hasOwn(functionInfo.references, fullId) &&
(!isInScope ||
// If the parent scope is `_createMdxContent`, then this
// references a component we can add a check statement for.
(functionStack.length === 1 &&
functionStack[0].node.type === 'FunctionDeclaration' &&
isNamedFunction(functionStack[0].node, '_createMdxContent')))
) {
functionInfo.references[fullId] = {component: true, node}
}
if (!functionInfo.objects.includes(id) && !isInScope) {
functionInfo.objects.push(id)
}
}
// `<xml:thing>`.
else if (name.type === 'JSXNamespacedName') {
// Ignore namespaces.
}
// If the name is a valid ES identifier, and it doesn’t start with a
// lowercase letter, it’s a component.
// For example, `$foo`, `_bar`, `Baz` are all component names.
// But `foo` and `b-ar` are tag names.
else if (isIdentifierName(name.name) && !/^[a-z]/.test(name.name)) {
const id = name.name
if (!inScope(visitors.scopes, id)) {
// No need to add an error for an undefined layout — we use an
// `if` later.
if (
id !== 'MDXLayout' &&
!Object.hasOwn(functionInfo.references, id)
) {
functionInfo.references[id] = {component: true, node}
}
if (!functionInfo.components.includes(id)) {
functionInfo.components.push(id)
}
}
} else if (node.data && node.data._mdxExplicitJsx) {
// Do not turn explicit JSX into components from `_components`.
// As in, a given `h1` component is used for `# heading` (next case),
// but not for `<h1>heading</h1>`.
} else {
const id = name.name
if (!functionInfo.tags.includes(id)) {
functionInfo.tags.push(id)
}
/** @type {Array<number | string>} */
let jsxIdExpression = ['_components', id]
if (isIdentifierName(id) === false) {
let invalidComponentName =
functionInfo.idToInvalidComponentName.get(id)
if (invalidComponentName === undefined) {
invalidComponentName = `_component${functionInfo.idToInvalidComponentName.size}`
functionInfo.idToInvalidComponentName.set(
id,
invalidComponentName
)
}
jsxIdExpression = [invalidComponentName]
}
node.openingElement.name =
toJsxIdOrMemberExpression(jsxIdExpression)
if (node.closingElement) {
node.closingElement.name =
toJsxIdOrMemberExpression(jsxIdExpression)
}
}
}
},
leave(node) {
visitors.exit(node)
/** @type {Array<Property | SpreadElement>} */
const defaults = []
/** @type {Array<string>} */
const actual = []
/** @type {Array<Expression>} */
const parameters = []
/** @type {Array<VariableDeclarator>} */
const declarations = []
if (
node.type === 'FunctionDeclaration' ||
node.type === 'FunctionExpression' ||
node.type === 'ArrowFunctionExpression'
) {
const functionInfo = functionStack[functionStack.length - 1]
/** @type {string} */
let name
for (name of functionInfo.tags.sort()) {
defaults.push({
type: 'Property',
kind: 'init',
key: isIdentifierName(name)
? {type: 'Identifier', name}
: {type: 'Literal', value: name},
value: {type: 'Literal', value: name},
method: false,
shorthand: false,
computed: false
})
}
actual.push(...functionInfo.components)
for (name of functionInfo.objects) {
// In some cases, a component is used directly (`<X>`) but it’s also
// used as an object (`<X.Y>`).
if (!actual.includes(name)) {
actual.push(name)
}
}
actual.sort()
/** @type {Array<Statement>} */
const statements = []
if (
defaults.length > 0 ||
actual.length > 0 ||
functionInfo.idToInvalidComponentName.size > 0
) {
if (providerImportSource) {
importProvider = true
parameters.push({
type: 'CallExpression',
callee: {type: 'Identifier', name: '_provideComponents'},
arguments: [],
optional: false
})
}
// Accept `components` as a prop if this is the `MDXContent` or
// `_createMdxContent` function.
if (
isNamedFunction(functionInfo.node, 'MDXContent') ||
isNamedFunction(functionInfo.node, '_createMdxContent')
) {
parameters.push(toIdOrMemberExpression(['props', 'components']))
}
if (defaults.length > 0 || parameters.length > 1) {
for (const parameter of parameters) {
defaults.push({type: 'SpreadElement', argument: parameter})
}
}
// If we’re getting components from several sources, merge them.
/** @type {Expression} */
let componentsInit =
defaults.length > 0
? {type: 'ObjectExpression', properties: defaults}
: // If we’re only getting components from `props.components`,
// make sure it’s defined.
{
type: 'LogicalExpression',
operator: '||',
left: parameters[0],
right: {type: 'ObjectExpression', properties: []}
}
/** @type {ObjectPattern | undefined} */
let componentsPattern
// Add components to scope.
// For `['MyComponent', 'MDXLayout']` this generates:
// ```tsx
// const {MyComponent, wrapper: MDXLayout} = _components
// ```
// Note that MDXLayout is special as it’s taken from
// `_components.wrapper`.
if (actual.length > 0) {
componentsPattern = {
type: 'ObjectPattern',
properties: actual.map(function (name) {
return {
type: 'Property',
kind: 'init',
key: {
type: 'Identifier',
name: name === 'MDXLayout' ? 'wrapper' : name
},
value: {type: 'Identifier', name},
method: false,
shorthand: name !== 'MDXLayout',
computed: false
}
})
}
}
if (functionInfo.tags.length > 0) {
declarations.push({
type: 'VariableDeclarator',
id: {type: 'Identifier', name: '_components'},
init: componentsInit
})
componentsInit = {type: 'Identifier', name: '_components'}
}
if (isNamedFunction(functionInfo.node, '_createMdxContent')) {
for (const [id, componentName] of [
...functionInfo.idToInvalidComponentName
].sort(function ([a], [b]) {
return a.localeCompare(b)
})) {
// For JSX IDs that can’t be represented as JavaScript IDs (as in,
// those with dashes, such as `custom-element`), generate a
// separate variable that is a valid JS ID (such as `_component0`),
// and takes it from components:
// `const _component0 = _components['custom-element']`
declarations.push({
type: 'VariableDeclarator',
id: {
type: 'Identifier',
name: componentName
},
init: {
type: 'MemberExpression',
object: {type: 'Identifier', name: '_components'},
property: {type: 'Literal', value: id},
computed: true,
optional: false
}
})
}
}
if (componentsPattern) {
declarations.push({
type: 'VariableDeclarator',
id: componentsPattern,
init: componentsInit
})
}
if (declarations.length > 0) {
statements.push({
type: 'VariableDeclaration',
kind: 'const',
declarations
})
}
}
/** @type {string} */
let key
// Add partials (so for `x.y.z` it’d generate `x` and `x.y` too).
for (key in functionInfo.references) {
if (Object.hasOwn(functionInfo.references, key)) {
const parts = key.split('.')
let index = 0
while (++index < parts.length) {
const partial = parts.slice(0, index).join('.')
if (!Object.hasOwn(functionInfo.references, partial)) {
functionInfo.references[partial] = {
component: false,
node: functionInfo.references[key].node
}
}
}
}
}
const references = Object.keys(functionInfo.references).sort()
let index = -1
while (++index < references.length) {
const id = references[index]
const info = functionInfo.references[id]
const place = stringifyPosition(positionFromEstree(info.node))
/** @type {Array<Expression>} */
const parameters = [
{type: 'Literal', value: id},
{type: 'Literal', value: info.component}
]
createErrorHelper = true
if (development && place) {
parameters.push({type: 'Literal', value: place})
}
statements.push({
type: 'IfStatement',
test: {
type: 'UnaryExpression',
operator: '!',
prefix: true,
argument: toIdOrMemberExpression(id.split('.'))
},
consequent: {
type: 'ExpressionStatement',
expression: {
type: 'CallExpression',
callee: {type: 'Identifier', name: '_missingMdxReference'},
arguments: parameters,
optional: false
}
},
alternate: undefined
})
}
if (statements.length > 0) {
// Arrow functions with an implied return:
if (node.body.type !== 'BlockStatement') {
node.body = {
type: 'BlockStatement',
body: [{type: 'ReturnStatement', argument: node.body}]
}
}
node.body.body.unshift(...statements)
}
functionStack.pop()
}
}
})
// If a provider is used (and can be used), import it.
if (importProvider && providerImportSource) {
tree.body.unshift(
createImportProvider(providerImportSource, outputFormat)
)
}
// If potentially missing components are used.
if (createErrorHelper) {
/** @type {Array<Expression>} */
const message = [
{type: 'Literal', value: 'Expected '},
{
type: 'ConditionalExpression',
test: {type: 'Identifier', name: 'component'},
consequent: {type: 'Literal', value: 'component'},
alternate: {type: 'Literal', value: 'object'}
},
{type: 'Literal', value: ' `'},
{type: 'Identifier', name: 'id'},
{
type: 'Literal',
value:
'` to be defined: you likely forgot to import, pass, or provide it.'
}
]
/** @type {Array<Identifier>} */
const parameters = [
{type: 'Identifier', name: 'id'},
{type: 'Identifier', name: 'component'}
]
if (development) {
message.push({
type: 'ConditionalExpression',
test: {type: 'Identifier', name: 'place'},
consequent: toBinaryAddition([
{type: 'Literal', value: '\nIt’s referenced in your code at `'},
{type: 'Identifier', name: 'place'},
{
type: 'Literal',
value: (file.path ? '` in `' + file.path : '') + '`'
}
]),
alternate: {type: 'Literal', value: ''}
})
parameters.push({type: 'Identifier', name: 'place'})
}
tree.body.push({
type: 'FunctionDeclaration',
id: {type: 'Identifier', name: '_missingMdxReference'},
generator: false,
async: false,
params: parameters,
body: {
type: 'BlockStatement',
body: [
{
type: 'ThrowStatement',
argument: {
type: 'NewExpression',
callee: {type: 'Identifier', name: 'Error'},
arguments: [toBinaryAddition(message)]
}
}
]
}
})
}
if (outputFormat === 'function-body') {
tree.body.unshift({
type: 'ExpressionStatement',
expression: {type: 'Literal', value: 'use strict'},
directive: 'use strict'
})
}
}
}
/**
* @param {string} providerImportSource
* Provider source.
* @param {'function-body' | 'program' | null | undefined} outputFormat
* Format.
* @returns {ModuleDeclaration | Statement}
* Node.
*/
function createImportProvider(providerImportSource, outputFormat) {
/** @type {Array<ImportSpecifier>} */
const specifiers = [
{
type: 'ImportSpecifier',
imported: {type: 'Identifier', name: 'useMDXComponents'},
local: {type: 'Identifier', name: '_provideComponents'}
}
]
return outputFormat === 'function-body'
? {
type: 'VariableDeclaration',
kind: 'const',
declarations: specifiersToDeclarations(
specifiers,
toIdOrMemberExpression(['arguments', 0])
)
}
: {
type: 'ImportDeclaration',
specifiers,
attributes: [],
source: {type: 'Literal', value: providerImportSource}
}
}
/**
* @param {Readonly<EstreeFunction>} node
* Node.
* @param {string} name
* Name.
* @returns {boolean}
* Whether `node` is a named function with `name`.
*/
function isNamedFunction(node, name) {
return Boolean(node && 'id' in node && node.id && node.id.name === name)
}
/**
* @param {Array<Scope>} scopes
* Scope.
* @param {string} id
* Identifier.
* @returns {boolean}
* Whether `id` is in `scope`.
*/
function inScope(scopes, id) {
let index = scopes.length
while (index--) {
const scope = scopes[index]
if (scope.defined.includes(id)) {
return true
}
}
return false
}
@@ -0,0 +1,31 @@
/**
* @import {Root} from 'hast'
*/
import {visit} from 'unist-util-visit'
/**
* A tiny plugin that removes raw HTML.
*
* This is needed if the format is `md` and `rehype-raw` was not used to parse
* dangerous HTML into nodes.
*
* @returns
* Transform.
*/
export function rehypeRemoveRaw() {
/**
* @param {Root} tree
* Tree.
* @returns {undefined}
* Nothing.
*/
return function (tree) {
visit(tree, 'raw', function (_, index, parent) {
if (parent && typeof index === 'number') {
parent.children.splice(index, 1)
return index
}
})
}
}
@@ -0,0 +1,114 @@
/**
* @import {Root, RootContent} from 'mdast'
*/
import {collapseWhiteSpace} from 'collapse-white-space'
import {walk} from 'estree-walker'
import {visit} from 'unist-util-visit'
/**
* A tiny plugin that unravels `<p><h1>x</h1></p>` but also
* `<p><Component /></p>` (so it has no knowledge of “HTML”).
*
* It also marks JSX as being explicitly JSX, so when a user passes a `h1`
* component, it is used for `# heading` but not for `<h1>heading</h1>`.
*
* @returns
* Transform.
*/
export function remarkMarkAndUnravel() {
/**
* @param {Root} tree
* Tree.
* @returns {undefined}
* Nothing.
*/
return function (tree) {
visit(tree, function (node, index, parent) {
let offset = -1
let all = true
let oneOrMore = false
if (parent && typeof index === 'number' && node.type === 'paragraph') {
const children = node.children
while (++offset < children.length) {
const child = children[offset]
if (
child.type === 'mdxJsxTextElement' ||
child.type === 'mdxTextExpression'
) {
oneOrMore = true
} else if (
child.type === 'text' &&
collapseWhiteSpace(child.value, {style: 'html', trim: true}) === ''
) {
// Empty.
} else {
all = false
break
}
}
if (all && oneOrMore) {
offset = -1
/** @type {Array<RootContent>} */
const newChildren = []
while (++offset < children.length) {
const child = children[offset]
if (child.type === 'mdxJsxTextElement') {
// @ts-expect-error: mutate because it is faster; content model is fine.
child.type = 'mdxJsxFlowElement'
}
if (child.type === 'mdxTextExpression') {
// @ts-expect-error: mutate because it is faster; content model is fine.
child.type = 'mdxFlowExpression'
}
if (
child.type === 'text' &&
/^[\t\r\n ]+$/.test(String(child.value))
) {
// Empty.
} else {
newChildren.push(child)
}
}
parent.children.splice(index, 1, ...newChildren)
return index
}
}
if (
node.type === 'mdxJsxFlowElement' ||
node.type === 'mdxJsxTextElement'
) {
const data = node.data || (node.data = {})
data._mdxExplicitJsx = true
}
if (
(node.type === 'mdxFlowExpression' ||
node.type === 'mdxTextExpression' ||
node.type === 'mdxjsEsm') &&
node.data &&
node.data.estree
) {
walk(node.data.estree, {
enter(node) {
if (node.type === 'JSXElement') {
const data = node.data || (node.data = {})
data._mdxExplicitJsx = true
}
}
})
}
})
}
}
@@ -0,0 +1,44 @@
/**
* @import {MDXModule} from 'mdx/types.js'
* @import {RunOptions} from './util/resolve-evaluate-options.js'
*/
/** @type {new (code: string, ...args: Array<unknown>) => Function} **/
const AsyncFunction = Object.getPrototypeOf(run).constructor
/**
* Run code compiled with `outputFormat: 'function-body'`.
*
* > ☢️ **Danger**: this `eval`s JavaScript.
*
* @param {{toString(): string}} code
* JavaScript function body to run.
* @param {RunOptions} options
* Configuration (**required**).
* @return {Promise<MDXModule>}
* Promise to a module;
* the result is an object with a `default` field set to the component;
* anything else that was exported is available too.
*/
export async function run(code, options) {
return new AsyncFunction(String(code))(options)
}
/**
* Run code, synchronously.
*
* When possible please use the async `run`.
*
* > ☢️ **Danger**: this `eval`s JavaScript.
*
* @param {{toString(): string}} code
* JavaScript function body to run.
* @param {RunOptions} options
* Configuration (**required**).
* @return {MDXModule}
* Module.
*/
export function runSync(code, options) {
// eslint-disable-next-line no-new-func
return new Function(String(code))(options)
}
@@ -0,0 +1,29 @@
/**
* @import {Node} from 'estree-jsx'
*/
// Fix to show references to above types in VS Code.
''
/**
* @param {Readonly<Node>} from
* Node to take from.
* @param {Node} to
* Node to add to.
* @returns {undefined}
* Nothing.
*/
export function create(from, to) {
/** @type {Array<keyof Node>} */
const fields = ['start', 'end', 'loc', 'range']
let index = -1
while (++index < fields.length) {
const field = fields[index]
if (field in from) {
// @ts-expect-error: assume they’re settable.
to[field] = from[field]
}
}
}
@@ -0,0 +1,33 @@
/**
* @import {
Declaration,
Expression,
MaybeNamedClassDeclaration,
MaybeNamedFunctionDeclaration
* } from 'estree-jsx'
*/
import {ok as assert} from 'devlop'
/**
* Turn a declaration into an expression.
*
* Doesn’t work for variable declarations, but that’s fine for our use case
* because currently we’re using this utility for export default declarations,
* which can’t contain variable declarations.
*
* @param {Readonly<Declaration | MaybeNamedClassDeclaration | MaybeNamedFunctionDeclaration>} declaration
* Declaration.
* @returns {Expression}
* Expression.
*/
export function declarationToExpression(declaration) {
if (declaration.type === 'FunctionDeclaration') {
return {...declaration, type: 'FunctionExpression'}
}
// This is currently an internal utility so the next shouldn’t happen or a
// maintainer is making a mistake.
assert(declaration.type === 'ClassDeclaration', 'unexpected node type')
return {...declaration, type: 'ClassExpression'}
}
@@ -0,0 +1,27 @@
/**
* @import {
Declaration,
MaybeNamedClassDeclaration,
MaybeNamedFunctionDeclaration,
Node
* } from 'estree-jsx'
*/
// Fix to show references to above types in VS Code.
''
/**
* Check if `node` is a declaration.
*
* @param {Readonly<MaybeNamedClassDeclaration | MaybeNamedFunctionDeclaration | Node>} node
* Node to check.
* @returns {node is Declaration | MaybeNamedClassDeclaration | MaybeNamedFunctionDeclaration}
* Whether `node` is a declaration.
*/
export function isDeclaration(node) {
return Boolean(
node.type === 'FunctionDeclaration' ||
node.type === 'ClassDeclaration' ||
node.type === 'VariableDeclaration'
)
}
@@ -0,0 +1,104 @@
/**
* @import {
AssignmentProperty,
ExportSpecifier,
Expression,
Identifier,
ImportDefaultSpecifier,
ImportNamespaceSpecifier,
ImportSpecifier,
Literal,
VariableDeclarator
* } from 'estree-jsx'
*/
import {ok as assert} from 'devlop'
import {create} from './estree-util-create.js'
/**
* @param {ReadonlyArray<Readonly<ExportSpecifier> | Readonly<ImportDefaultSpecifier> | Readonly<ImportNamespaceSpecifier> | Readonly<ImportSpecifier>>} specifiers
* Specifiers.
* @param {Readonly<Expression>} init
* Initializer.
* @returns {Array<VariableDeclarator>}
* Declarations.
*/
export function specifiersToDeclarations(specifiers, init) {
let index = -1
/** @type {Array<VariableDeclarator>} */
const declarations = []
/** @type {Array<ExportSpecifier | ImportDefaultSpecifier | ImportSpecifier>} */
const otherSpecifiers = []
// Can only be one according to JS syntax.
/** @type {ImportNamespaceSpecifier | undefined} */
let importNamespaceSpecifier
while (++index < specifiers.length) {
const specifier = specifiers[index]
if (specifier.type === 'ImportNamespaceSpecifier') {
importNamespaceSpecifier = specifier
} else {
otherSpecifiers.push(specifier)
}
}
if (importNamespaceSpecifier) {
/** @type {VariableDeclarator} */
const declarator = {
type: 'VariableDeclarator',
id: importNamespaceSpecifier.local,
init
}
create(importNamespaceSpecifier, declarator)
declarations.push(declarator)
}
declarations.push({
type: 'VariableDeclarator',
id: {
type: 'ObjectPattern',
properties: otherSpecifiers.map(function (specifier) {
/** @type {Identifier | Literal} */
let key =
specifier.type === 'ImportSpecifier'
? specifier.imported
: specifier.type === 'ExportSpecifier'
? specifier.exported
: {type: 'Identifier', name: 'default'}
let value = specifier.local
// Switch them around if we’re exporting.
if (specifier.type === 'ExportSpecifier') {
value = key
key = specifier.local
}
// To do: what to do about literals?
// `const { a: 'b' } = c()` does not work?
assert(value.type === 'Identifier')
/** @type {AssignmentProperty} */
const property = {
type: 'Property',
kind: 'init',
shorthand:
key.type === 'Identifier' &&
value.type === 'Identifier' &&
key.name === value.name,
method: false,
computed: false,
key,
value
}
create(specifier, property)
return property
})
},
init: importNamespaceSpecifier
? {type: 'Identifier', name: importNamespaceSpecifier.local.name}
: init
})
return declarations
}
@@ -0,0 +1,25 @@
/**
* @import {Expression} from 'estree-jsx'
*/
import {ok as assert} from 'devlop'
/**
* @param {ReadonlyArray<Expression>} expressions
* Expressions.
* @returns {Expression}
* Addition.
*/
export function toBinaryAddition(expressions) {
let index = -1
/** @type {Expression | undefined} */
let left
while (++index < expressions.length) {
const right = expressions[index]
left = left ? {type: 'BinaryExpression', left, operator: '+', right} : right
}
assert(left, 'expected non-empty `expressions` to be passed')
return left
}
@@ -0,0 +1,73 @@
/**
* @import {
Identifier,
JSXIdentifier,
JSXMemberExpression,
Literal,
MemberExpression
* } from 'estree-jsx'
*/
import {ok as assert} from 'devlop'
import {name as isIdentifierName} from 'estree-util-is-identifier-name'
/**
* @param {ReadonlyArray<number | string>} ids
* Identifiers (example: `['list', 0]).
* @returns {Identifier | MemberExpression}
* Identifier or member expression.
*/
export function toIdOrMemberExpression(ids) {
let index = -1
/** @type {Identifier | Literal | MemberExpression | undefined} */
let object
while (++index < ids.length) {
const name = ids[index]
/** @type {Identifier | Literal} */
const id =
typeof name === 'string' && isIdentifierName(name)
? {type: 'Identifier', name}
: {type: 'Literal', value: name}
object = object
? {
type: 'MemberExpression',
object,
property: id,
computed: id.type === 'Literal',
optional: false
}
: id
}
assert(object, 'expected non-empty `ids` to be passed')
assert(object.type !== 'Literal', 'expected identifier as left-most value')
return object
}
/**
* @param {ReadonlyArray<number | string>} ids
* Identifiers (example: `['list', 0]).
* @returns {JSXIdentifier | JSXMemberExpression}
* Identifier or member expression.
*/
export function toJsxIdOrMemberExpression(ids) {
let index = -1
/** @type {JSXIdentifier | JSXMemberExpression | undefined} */
let object
while (++index < ids.length) {
const name = ids[index]
assert(
typeof name === 'string' && isIdentifierName(name, {jsx: true}),
'expected valid jsx identifier, not `' + name + '`'
)
/** @type {JSXIdentifier} */
const id = {type: 'JSXIdentifier', name}
object = object ? {type: 'JSXMemberExpression', object, property: id} : id
}
assert(object, 'expected non-empty `ids` to be passed')
return object
}
@@ -0,0 +1,6 @@
import markdownExtensions from 'markdown-extensions'
export const md = markdownExtensions.map(function (d) {
return '.' + d
})
export const mdx = ['.mdx']
@@ -0,0 +1,87 @@
/**
* @import {Fragment, Jsx, JsxDev} from 'hast-util-to-jsx-runtime'
* @import {MDXComponents} from 'mdx/types.js'
* @import {CompileOptions} from '../compile.js'
*/
/**
* @typedef {EvaluateProcessorOptions & RunOptions} EvaluateOptions
* Configuration for `evaluate`.
*
* @typedef {Omit<CompileOptions, 'baseUrl' | 'jsx' | 'jsxImportSource' | 'jsxRuntime' | 'outputFormat' | 'pragma' | 'pragmaFrag' | 'pragmaImportSource' | 'providerImportSource'> } EvaluateProcessorOptions
* Compile configuration without JSX options for evaluation.
*
* @typedef RunOptions
* Configuration to run compiled code.
*
* `Fragment`, `jsx`, and `jsxs` are used when the code is compiled in
* production mode (`development: false`).
* `Fragment` and `jsxDEV` are used when compiled in development mode
* (`development: true`).
* `useMDXComponents` is used when the code is compiled with
* `providerImportSource: '#'` (the exact value of this compile option
* doesn’t matter).
* @property {URL | string | null | undefined} [baseUrl]
* Use this URL as `import.meta.url` and resolve `import` and `export … from`
* relative to it (optional, example: `import.meta.url`);
* this option can also be given at compile time in `CompileOptions`;
* you should pass this (likely at runtime), as you might get runtime errors
* when using `import.meta.url` / `import` / `export … from ` otherwise.
* @property {Fragment} Fragment
* Symbol to use for fragments (**required**).
* @property {Jsx | null | undefined} [jsx]
* Function to generate an element with static children in production mode.
* @property {JsxDev | null | undefined} [jsxDEV]
* Function to generate an element in development mode.
* @property {Jsx | null | undefined} [jsxs]
* Function to generate an element with dynamic children in production mode.
* @property {UseMdxComponents | null | undefined} [useMDXComponents]
* Function to get components from context.
*
* @callback UseMdxComponents
* Get components from context.
* @returns {MDXComponents}
* Current components.
*/
// Fix to show references to above types in VS Code.
''
/**
* Split compiletime options from runtime options.
*
* @param {Readonly<EvaluateOptions> | null | undefined} options
* Configuration.
* @returns {{compiletime: CompileOptions, runtime: RunOptions}}
* Split options.
*/
export function resolveEvaluateOptions(options) {
const {
Fragment,
baseUrl,
development,
jsx,
jsxDEV,
jsxs,
useMDXComponents,
...rest
} = options || {}
if (!Fragment) throw new Error('Expected `Fragment` given to `evaluate`')
if (development) {
if (!jsxDEV) throw new Error('Expected `jsxDEV` given to `evaluate`')
} else {
if (!jsx) throw new Error('Expected `jsx` given to `evaluate`')
if (!jsxs) throw new Error('Expected `jsxs` given to `evaluate`')
}
return {
compiletime: {
...rest,
development,
outputFormat: 'function-body',
providerImportSource: useMDXComponents ? '#' : undefined
},
runtime: {Fragment, baseUrl, jsx, jsxDEV, jsxs, useMDXComponents}
}
}
@@ -0,0 +1,53 @@
/**
* @import {Compatible} from 'vfile'
* @import {CompileOptions} from '../compile.js'
* @import {ProcessorOptions} from '../core.js'
*/
import {VFile} from 'vfile'
import {md} from './extnames.js'
/**
* Create a file and options from a given `vfileCompatible` and options that
* might contain `format: 'detect'`.
*
* @param {Readonly<Compatible>} vfileCompatible
* File.
* @param {Readonly<CompileOptions> | null | undefined} [options]
* Configuration (optional).
* @returns {{file: VFile, options: ProcessorOptions}}
* File and options.
*/
export function resolveFileAndOptions(vfileCompatible, options) {
const file = looksLikeAVFile(vfileCompatible)
? vfileCompatible
: new VFile(vfileCompatible)
const {format, ...rest} = options || {}
return {
file,
options: {
format:
format === 'md' || format === 'mdx'
? format
: file.extname && (rest.mdExtensions || md).includes(file.extname)
? 'md'
: 'mdx',
...rest
}
}
}
/**
* @param {Readonly<Compatible> | null | undefined} [value]
* Thing.
* @returns {value is VFile}
* Check.
*/
function looksLikeAVFile(value) {
return Boolean(
value &&
typeof value === 'object' &&
'message' in value &&
'messages' in value
)
}
@@ -0,0 +1,108 @@
{
"name": "@mdx-js/mdx",
"version": "3.1.1",
"description": "MDX compiler",
"license": "MIT",
"keywords": [
"jsx",
"markdown",
"mdx",
"remark"
],
"homepage": "https://mdxjs.com",
"repository": {
"type": "git",
"url": "https://github.com/mdx-js/mdx",
"directory": "packages/mdx/"
},
"bugs": "https://github.com/mdx-js/mdx/issues",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
},
"author": "John Otander <johnotander@gmail.com> (https://johno.com)",
"contributors": [
"John Otander <johnotander@gmail.com> (https://johno.com)",
"Tim Neutkens <tim@vercel.com>",
"Matija Marohnić <matija.marohnic@gmail.com>",
"Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
"JounQin <admin@1stg.me> (https://www.1stg.me)",
"Christian Murphy <christian.murphy.42@gmail.com>"
],
"type": "module",
"sideEffects": false,
"exports": {
".": "./index.js",
"./internal-create-format-aware-processors": "./lib/util/create-format-aware-processors.js",
"./internal-extnames-to-regex": "./lib/util/extnames-to-regex.js"
},
"files": [
"lib/",
"index.d.ts.map",
"index.d.ts",
"index.js"
],
"dependencies": {
"@types/estree": "^1.0.0",
"@types/estree-jsx": "^1.0.0",
"@types/hast": "^3.0.0",
"@types/mdx": "^2.0.0",
"acorn": "^8.0.0",
"collapse-white-space": "^2.0.0",
"devlop": "^1.0.0",
"estree-util-is-identifier-name": "^3.0.0",
"estree-util-scope": "^1.0.0",
"estree-walker": "^3.0.0",
"hast-util-to-jsx-runtime": "^2.0.0",
"markdown-extensions": "^2.0.0",
"recma-build-jsx": "^1.0.0",
"recma-jsx": "^1.0.0",
"recma-stringify": "^1.0.0",
"rehype-recma": "^1.0.0",
"remark-mdx": "^3.0.0",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.0.0",
"source-map": "^0.7.0",
"unified": "^11.0.0",
"unist-util-position-from-estree": "^2.0.0",
"unist-util-stringify-position": "^4.0.0",
"unist-util-visit": "^5.0.0",
"vfile": "^6.0.0"
},
"scripts": {
"test": "npm run test-coverage",
"test-api": "node --conditions development --enable-source-maps test/index.js",
"test-coverage": "c8 --100 --reporter lcov npm run test-api"
},
"xo": {
"overrides": [
{
"files": [
"test/**/*.js"
],
"rules": {
"no-restricted-globals": "off"
}
},
{
"files": [
"**/*.ts"
],
"rules": {
"@typescript-eslint/array-type": "off",
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/consistent-type-definitions": "off"
}
}
],
"prettier": true,
"rules": {
"complexity": "off",
"logical-assignment-operators": "off",
"max-depth": "off",
"n/file-extension-in-import": "off",
"unicorn/prefer-at": "off",
"unicorn/prefer-code-point": "off"
}
}
}
@@ -0,0 +1 @@
../../collapse-white-space@2.1.0/node_modules/collapse-white-space
+1
View File
@@ -0,0 +1 @@
../../devlop@1.1.0/node_modules/devlop
@@ -0,0 +1 @@
../../estree-util-is-identifier-name@3.0.0/node_modules/estree-util-is-identifier-name
@@ -0,0 +1 @@
../../estree-util-scope@1.0.0/node_modules/estree-util-scope
+1
View File
@@ -0,0 +1 @@
../../estree-walker@3.0.3/node_modules/estree-walker
@@ -0,0 +1 @@
../../markdown-extensions@2.0.0/node_modules/markdown-extensions
@@ -0,0 +1 @@
../../recma-build-jsx@1.0.0/node_modules/recma-build-jsx
+1
View File
@@ -0,0 +1 @@
../../recma-jsx@1.0.1_acorn@8.16.0/node_modules/recma-jsx
@@ -0,0 +1 @@
../../recma-stringify@1.0.0/node_modules/recma-stringify
+1
View File
@@ -0,0 +1 @@
../../rehype-recma@1.0.0/node_modules/rehype-recma
+1
View File
@@ -0,0 +1 @@
../../remark-mdx@3.1.1/node_modules/remark-mdx
+1
View File
@@ -0,0 +1 @@
../../remark-parse@11.0.0/node_modules/remark-parse
+1
View File
@@ -0,0 +1 @@
../../remark-rehype@11.1.2/node_modules/remark-rehype
+1
View File
@@ -0,0 +1 @@
../../unified@11.0.5/node_modules/unified
@@ -0,0 +1 @@
../../unist-util-position-from-estree@2.0.0/node_modules/unist-util-position-from-estree
@@ -0,0 +1 @@
../../unist-util-stringify-position@4.0.0/node_modules/unist-util-stringify-position
@@ -0,0 +1 @@
../../unist-util-visit@5.1.0/node_modules/unist-util-visit
+1
View File
@@ -0,0 +1 @@
../../vfile@6.0.3/node_modules/vfile