Comparing version 1.5.1 to 1.6.0
@@ -11,2 +11,3 @@ /** | ||
* @property {'mdx' | 'md'} [format='mdx'] Format of the files to be processed | ||
* @property {'program' | 'function-body'} [outputFormat='program'] Whether to compile to a whole program or a function body. | ||
* @property {string[]} [mdExtensions] Extensions (with `.`) for markdown | ||
@@ -17,5 +18,4 @@ * @property {string[]} [mdxExtensions] Extensions (with `.`) for MDX | ||
* @property {PluggableList} [rehypePlugins] List of rehype (hast, HTML) plugins | ||
* @property {boolean} [_contain=false] Semihidden option | ||
* | ||
* @typedef {Omit<RecmaDocumentOptions & RecmaStringifyOptions & RecmaJsxRewriteOptions, '_contain'>} PluginOptions | ||
* @typedef {Omit<RecmaDocumentOptions & RecmaStringifyOptions & RecmaJsxRewriteOptions, 'outputFormat'>} PluginOptions | ||
* @typedef {BaseProcessorOptions & PluginOptions} ProcessorOptions | ||
@@ -49,2 +49,6 @@ */ | ||
/** | ||
* Whether to compile to a whole program or a function body. | ||
*/ | ||
outputFormat?: 'program' | 'function-body'; | ||
/** | ||
* Extensions (with `.`) for markdown | ||
@@ -69,8 +73,4 @@ */ | ||
rehypePlugins?: PluggableList; | ||
/** | ||
* Semihidden option | ||
*/ | ||
_contain?: boolean; | ||
}; | ||
export type PluginOptions = Omit<RecmaDocumentOptions & RecmaStringifyOptions & RecmaJsxRewriteOptions, '_contain'>; | ||
export type PluginOptions = Omit<RecmaDocumentOptions & RecmaStringifyOptions & RecmaJsxRewriteOptions, 'outputFormat'>; | ||
export type ProcessorOptions = BaseProcessorOptions & PluginOptions; |
@@ -24,2 +24,3 @@ import unified from 'unified' | ||
* @property {'mdx' | 'md'} [format='mdx'] Format of the files to be processed | ||
* @property {'program' | 'function-body'} [outputFormat='program'] Whether to compile to a whole program or a function body. | ||
* @property {string[]} [mdExtensions] Extensions (with `.`) for markdown | ||
@@ -30,5 +31,4 @@ * @property {string[]} [mdxExtensions] Extensions (with `.`) for MDX | ||
* @property {PluggableList} [rehypePlugins] List of rehype (hast, HTML) plugins | ||
* @property {boolean} [_contain=false] Semihidden option | ||
* | ||
* @typedef {Omit<RecmaDocumentOptions & RecmaStringifyOptions & RecmaJsxRewriteOptions, '_contain'>} PluginOptions | ||
* @typedef {Omit<RecmaDocumentOptions & RecmaStringifyOptions & RecmaJsxRewriteOptions, 'outputFormat'>} PluginOptions | ||
* @typedef {BaseProcessorOptions & PluginOptions} ProcessorOptions | ||
@@ -49,5 +49,5 @@ */ | ||
var { | ||
_contain, | ||
jsx, | ||
format, | ||
outputFormat, | ||
providerImportSource, | ||
@@ -78,7 +78,7 @@ recmaPlugins, | ||
.use(rehypeRecma) | ||
.use(recmaDocument, {...rest, _contain}) | ||
.use(recmaDocument, {...rest, outputFormat}) | ||
// @ts-ignore recma transformer uses an esast node rather than a unist node | ||
.use(recmaJsxRewrite, {providerImportSource, _contain}) | ||
.use(recmaJsxRewrite, {providerImportSource, outputFormat}) | ||
// @ts-ignore recma transformer uses an esast node rather than a unist node | ||
.use(jsx ? undefined : recmaJsxBuild, {_contain}) | ||
.use(jsx ? undefined : recmaJsxBuild, {outputFormat}) | ||
// @ts-ignore recma compiler is seen as a transformer | ||
@@ -85,0 +85,0 @@ .use(recmaStringify, {SourceMapGenerator}) |
@@ -19,2 +19,5 @@ import vfile from 'vfile' | ||
/** | ||
* @param {import('esbuild').PluginBuild} build | ||
*/ | ||
function setup(build) { | ||
@@ -24,18 +27,32 @@ build.onLoad({filter: extnamesToRegex(extnames)}, onload) | ||
/** | ||
* @param {import('esbuild').OnLoadArgs} data | ||
*/ | ||
async function onload(data) { | ||
var doc = String(await fs.readFile(data.path)) | ||
var file = vfile({contents: doc, path: data.path}) | ||
/** @type {import('vfile-message').VFileMessage[]} */ | ||
var messages = [] | ||
var errors = [] | ||
var warnings = [] | ||
/** @type {import('vfile').VFileContents} */ | ||
var contents | ||
/** @type {import('vfile-message').VFileMessage} */ | ||
var message | ||
/** @type {import('unist').Point} */ | ||
var start | ||
/** @type {import('unist').Point} */ | ||
var end | ||
var list | ||
/** @type {number} */ | ||
var length | ||
/** @type {number} */ | ||
var lineStart | ||
/** @type {number} */ | ||
var lineEnd | ||
/** @type {RegExpExecArray} */ | ||
var match | ||
/** @type {number} */ | ||
var line | ||
/** @type {number} */ | ||
var column | ||
@@ -42,0 +59,0 @@ |
/** | ||
* @typedef RecmaDocumentOptions | ||
* @property {boolean} [_contain] Semihidden option which here results in failing on imports and adding a top-level return statement instead of an export. | ||
* @property {string} [baseUrl] In `evaluate`, resolve relative import statements (and `export from`s) relative to this URL | ||
* @property {'program' | 'function-body'} [outputFormat='program'] Whether to use either `import` and `export` statements to get the runtime (and optionally provider) and export the content, or get values from `arguments` and return things | ||
* @property {boolean} [useDynamicImport=false] Whether to keep `import` (and `export … from`) statements or compile them to dynamic `import()` instead | ||
* @property {string} [baseUrl] Resolve relative `import` (and `export … from`) relative to this URL | ||
* @property {string} [pragma='React.createElement'] Pragma for JSX (used in classic runtime) | ||
@@ -16,11 +17,15 @@ * @property {string} [pragmaFrag='React.Fragment'] Pragma for JSX fragments (used in classic runtime) | ||
*/ | ||
export function recmaDocument(options?: RecmaDocumentOptions): (tree: any, file: any) => void; | ||
export function recmaDocument(options?: RecmaDocumentOptions): (tree: any, file: import('vfile').VFile) => void; | ||
export type RecmaDocumentOptions = { | ||
/** | ||
* Semihidden option which here results in failing on imports and adding a top-level return statement instead of an export. | ||
* Whether to use either `import` and `export` statements to get the runtime (and optionally provider) and export the content, or get values from `arguments` and return things | ||
*/ | ||
_contain?: boolean; | ||
outputFormat?: 'program' | 'function-body'; | ||
/** | ||
* In `evaluate`, resolve relative import statements (and `export from`s) relative to this URL | ||
* Whether to keep `import` (and `export … from`) statements or compile them to dynamic `import()` instead | ||
*/ | ||
useDynamicImport?: boolean; | ||
/** | ||
* Resolve relative `import` (and `export … from`) relative to this URL | ||
*/ | ||
baseUrl?: string; | ||
@@ -27,0 +32,0 @@ /** |
@@ -9,4 +9,5 @@ import u from 'unist-builder' | ||
* @typedef RecmaDocumentOptions | ||
* @property {boolean} [_contain] Semihidden option which here results in failing on imports and adding a top-level return statement instead of an export. | ||
* @property {string} [baseUrl] In `evaluate`, resolve relative import statements (and `export from`s) relative to this URL | ||
* @property {'program' | 'function-body'} [outputFormat='program'] Whether to use either `import` and `export` statements to get the runtime (and optionally provider) and export the content, or get values from `arguments` and return things | ||
* @property {boolean} [useDynamicImport=false] Whether to keep `import` (and `export … from`) statements or compile them to dynamic `import()` instead | ||
* @property {string} [baseUrl] Resolve relative `import` (and `export … from`) relative to this URL | ||
* @property {string} [pragma='React.createElement'] Pragma for JSX (used in classic runtime) | ||
@@ -26,4 +27,5 @@ * @property {string} [pragmaFrag='React.Fragment'] Pragma for JSX fragments (used in classic runtime) | ||
var { | ||
_contain, | ||
baseUrl, | ||
useDynamicImport, | ||
outputFormat = 'program', | ||
pragma = 'React.createElement', | ||
@@ -38,2 +40,6 @@ pragmaFrag = 'React.Fragment', | ||
/** | ||
* @param {*} tree | ||
* @param {import('vfile').VFile} file | ||
*/ | ||
function transform(tree, file) { | ||
@@ -43,2 +49,3 @@ var exportedIdentifiers = [] | ||
var pragmas = [] | ||
var exportAllCount = 0 | ||
var layout | ||
@@ -75,3 +82,3 @@ var content | ||
handleImport( | ||
handleEsm( | ||
u('ImportDeclaration', { | ||
@@ -121,71 +128,55 @@ specifiers: [ | ||
// ```js | ||
// export function a() {} | ||
// export {a, b as c} | ||
// export {a, b as c} from 'd' | ||
// ``` | ||
else if (child.type === 'ExportNamedDeclaration') { | ||
// ```js | ||
// export {a, b as c} from 'd' | ||
// ``` | ||
if (child.source) { | ||
// Remove `default` or `as default`, but not `default as`, specifier. | ||
child.specifiers = child.specifiers.filter(function (specifier) { | ||
if (specifier.exported.name === 'default') { | ||
if (layout) { | ||
file.fail( | ||
'Cannot specify multiple layouts (previous: ' + | ||
stringifyPosition(positionFromEstree(layout)) + | ||
')', | ||
positionFromEstree(child), | ||
'recma-document:duplicate-layout' | ||
) | ||
} | ||
else if (child.type === 'ExportNamedDeclaration' && child.source) { | ||
// Remove `default` or `as default`, but not `default as`, specifier. | ||
child.specifiers = child.specifiers.filter(function (specifier) { | ||
if (specifier.exported.name === 'default') { | ||
if (layout) { | ||
file.fail( | ||
'Cannot specify multiple layouts (previous: ' + | ||
stringifyPosition(positionFromEstree(layout)) + | ||
')', | ||
positionFromEstree(child), | ||
'recma-document:duplicate-layout' | ||
) | ||
} | ||
layout = specifier | ||
layout = specifier | ||
// Make it just an import: `import MDXLayout from '…'`. | ||
handleImport( | ||
create( | ||
specifier, | ||
u('ImportDeclaration', { | ||
specifiers: [ | ||
// Default as default / something else as default. | ||
specifier.local.name === 'default' | ||
? u('ImportDefaultSpecifier', { | ||
// Make it just an import: `import MDXLayout from '…'`. | ||
handleEsm( | ||
create( | ||
specifier, | ||
u('ImportDeclaration', { | ||
specifiers: [ | ||
// Default as default / something else as default. | ||
specifier.local.name === 'default' | ||
? u('ImportDefaultSpecifier', { | ||
local: u('Identifier', {name: 'MDXLayout'}) | ||
}) | ||
: create( | ||
specifier.local, | ||
u('ImportSpecifier', { | ||
imported: specifier.local, | ||
local: u('Identifier', {name: 'MDXLayout'}) | ||
}) | ||
: create( | ||
specifier.local, | ||
u('ImportSpecifier', { | ||
imported: specifier.local, | ||
local: u('Identifier', {name: 'MDXLayout'}) | ||
}) | ||
) | ||
], | ||
source: create( | ||
child.source, | ||
u('Literal', {value: child.source.value}) | ||
) | ||
}) | ||
) | ||
) | ||
], | ||
source: create( | ||
child.source, | ||
u('Literal', {value: child.source.value}) | ||
) | ||
}) | ||
) | ||
) | ||
return false | ||
} | ||
return false | ||
} | ||
return true | ||
}) | ||
return true | ||
}) | ||
// If there are other things imported, keep it. | ||
if (child.specifiers.length > 0) { | ||
handleExport(child) | ||
} | ||
} | ||
// ```js | ||
// export function a() {} | ||
// export class A {} | ||
// export const a = 1 | ||
// export {a, b as c} | ||
// ``` | ||
else { | ||
// If there are other things imported, keep it. | ||
if (child.specifiers.length > 0) { | ||
handleExport(child) | ||
@@ -197,3 +188,6 @@ } | ||
// ``` | ||
else if (child.type === 'ExportAllDeclaration') { | ||
else if ( | ||
child.type === 'ExportNamedDeclaration' || | ||
child.type === 'ExportAllDeclaration' | ||
) { | ||
handleExport(child) | ||
@@ -204,3 +198,3 @@ } else if ( | ||
) { | ||
handleImport(child) | ||
handleEsm(child) | ||
} else if ( | ||
@@ -228,15 +222,27 @@ child.type === 'ExpressionStatement' && | ||
if (_contain) { | ||
exportedIdentifiers.push(['MDXContent', 'default']) | ||
exportedIdentifiers.push(['MDXContent', 'default']) | ||
if (outputFormat === 'function-body') { | ||
replacement.push( | ||
u('ReturnStatement', { | ||
argument: u('ObjectExpression', { | ||
properties: exportedIdentifiers.map((d) => { | ||
return u('Property', { | ||
kind: 'init', | ||
shorthand: typeof d === 'string', | ||
key: u('Identifier', {name: typeof d === 'string' ? d : d[1]}), | ||
value: u('Identifier', {name: typeof d === 'string' ? d : d[0]}) | ||
properties: [].concat( | ||
Array.from({length: exportAllCount}).map((d, index) => | ||
u('SpreadElement', { | ||
argument: u('Identifier', {name: '_exportAll' + (index + 1)}) | ||
}) | ||
), | ||
exportedIdentifiers.map((d) => { | ||
return u('Property', { | ||
kind: 'init', | ||
shorthand: typeof d === 'string', | ||
key: u('Identifier', { | ||
name: typeof d === 'string' ? d : d[1] | ||
}), | ||
value: u('Identifier', { | ||
name: typeof d === 'string' ? d : d[0] | ||
}) | ||
}) | ||
}) | ||
}) | ||
) | ||
}) | ||
@@ -255,100 +261,127 @@ }) | ||
function handleImport(node) { | ||
if (_contain) { | ||
handleImportExportFrom(node) | ||
} else { | ||
replacement.push(node) | ||
} | ||
} | ||
function handleExport(node) { | ||
var child | ||
if (_contain) { | ||
// ```js | ||
// export function a() {} | ||
// export class A {} | ||
// export const a = 1 | ||
// ``` | ||
if (node.declaration) { | ||
replacement.push(node.declaration) | ||
if ( | ||
node.declaration.type === 'FunctionDeclaration' || | ||
node.declaration.type === 'ClassDeclaration' | ||
) { | ||
exportedIdentifiers.push(node.declaration.id.name) | ||
// ```js | ||
// export function a() {} | ||
// export class A {} | ||
// export var a = 1 | ||
// ``` | ||
if (node.declaration) { | ||
if ( | ||
node.declaration.type === 'FunctionDeclaration' || | ||
node.declaration.type === 'ClassDeclaration' | ||
) { | ||
exportedIdentifiers.push(node.declaration.id.name) | ||
} else { | ||
for (child of node.declaration.declarations) { | ||
exportedIdentifiers.push(child.id.name) | ||
} | ||
// Must be a variable declaration: other things can’t be exported with | ||
// ESM. | ||
else { | ||
for (child of node.declaration.declarations) { | ||
exportedIdentifiers.push(child.id.name) | ||
} | ||
} | ||
} else if (node.source) { | ||
handleImportExportFrom(node) | ||
} | ||
} | ||
// ```js | ||
// export {a, b as c} | ||
// export {a, b as c} from 'd' | ||
// ``` | ||
if (node.specifiers) { | ||
for (child of node.specifiers) { | ||
exportedIdentifiers.push( | ||
child.local.name === child.exported.name | ||
? child.local.name | ||
: [child.local.name, child.exported.name] | ||
) | ||
} | ||
// ```js | ||
// export {a, b as c} | ||
// export {a, b as c} from 'd' | ||
// ``` | ||
if (node.specifiers) { | ||
for (child of node.specifiers) { | ||
exportedIdentifiers.push(child.exported.name) | ||
} | ||
} else { | ||
replacement.push(node) | ||
} | ||
handleEsm(node) | ||
} | ||
function handleImportExportFrom(node) { | ||
function handleEsm(node) { | ||
var value | ||
var replace | ||
var id | ||
var declarations | ||
if (!baseUrl) { | ||
file.fail( | ||
'Cannot use `import` or `export … from` in `evaluateSync` or `evaluate` w/o passing `baseUrl`: use `compile`, `compileSync`, or use `evaluate` and pass a `baseUrl`', | ||
positionFromEstree(child), | ||
'recma-document:contain-export' | ||
) | ||
// Rewrite the source of the `import` / `export … from`. | ||
// See: <https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-specifier> | ||
if (baseUrl && node.source) { | ||
value = node.source.value | ||
try { | ||
// A full valid URL. | ||
value = new URL(value) | ||
} catch { | ||
// Relative: `/example.js`, `./example.js`, and `../example.js`. | ||
if (/^\.{0,2}\//.test(value)) { | ||
value = new URL(value, baseUrl) | ||
} | ||
// Otherwise, it’s a bare specifiers. | ||
// For example `some-package`, `@some-package`, and | ||
// `some-package/path`. | ||
// These are supported in Node and browsers plan to support them | ||
// with import maps (<https://github.com/WICG/import-maps>). | ||
} | ||
node.source = create(node.source, u('Literal', {value})) | ||
} | ||
value = node.source.value | ||
if (outputFormat === 'function-body') { | ||
if (node.source) { | ||
if (!useDynamicImport) { | ||
file.fail( | ||
'Cannot use `import` or `export … from` in `evaluate` (outputting a function body) by default: please set `useDynamicImport: true` (and probably specify a `baseUrl`)', | ||
positionFromEstree(node), | ||
'recma-document:invalid-esm-statement' | ||
) | ||
} | ||
// Relative. | ||
if (/^\.{0,2}\//.test(value)) { | ||
value = new URL(value, baseUrl) | ||
// ``` | ||
// 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') | ||
// ``` | ||
id = node.specifiers | ||
? specifiersToObjectPattern(node.specifiers) | ||
: u('Identifier', {name: '_exportAll' + ++exportAllCount}) | ||
replace = u('VariableDeclaration', { | ||
kind: 'const', | ||
declarations: [ | ||
u('VariableDeclarator', { | ||
id, | ||
init: u('AwaitExpression', { | ||
argument: create( | ||
node, | ||
u('ImportExpression', {source: node.source}) | ||
) | ||
}) | ||
}) | ||
] | ||
}) | ||
} else if (node.declaration) { | ||
replace = node.declaration | ||
} else { | ||
declarations = node.specifiers | ||
.filter( | ||
(specifier) => specifier.local.name !== specifier.exported.name | ||
) | ||
.map((specifier) => | ||
u('VariableDeclarator', { | ||
id: specifier.exported, | ||
init: specifier.local | ||
}) | ||
) | ||
if (declarations.length > 0) { | ||
replace = u('VariableDeclaration', {kind: 'const', declarations}) | ||
} | ||
} | ||
// Empty | ||
} else { | ||
// Bare specifiers such as `some-package`, `@some-package`, and | ||
// `some-package/path` will crash here. | ||
// Full URLs pass. | ||
try { | ||
value = new URL(value) | ||
} catch {} | ||
replace = node | ||
} | ||
replacement.push( | ||
u('VariableDeclaration', { | ||
kind: 'const', | ||
declarations: [ | ||
u('VariableDeclarator', { | ||
id: specifiersToObjectPattern(node.specifiers), | ||
init: u('AwaitExpression', { | ||
argument: create( | ||
node, | ||
u('ImportExpression', { | ||
source: create(node.source, u('Literal', {value})) | ||
}) | ||
) | ||
}) | ||
}) | ||
] | ||
}) | ||
) | ||
if (replace) { | ||
replacement.push(replace) | ||
} | ||
} | ||
@@ -355,0 +388,0 @@ } |
@@ -5,3 +5,3 @@ /** | ||
* @typedef RecmaJsxBuildOptions | ||
* @property {boolean} [_contain] Semihidden option which here results in getting the automatic runtime from `arguments[0]` instead of importing it | ||
* @property {'program' | 'function-body'} [outputFormat='program'] Whether to keep the import of the automatic runtime or get it from `arguments[0]` instead | ||
*/ | ||
@@ -18,5 +18,5 @@ /** | ||
/** | ||
* Semihidden option which here results in getting the automatic runtime from `arguments[0]` instead of importing it | ||
* Whether to keep the import of the automatic runtime or get it from `arguments[0]` instead | ||
*/ | ||
_contain?: boolean; | ||
outputFormat?: 'program' | 'function-body'; | ||
}; |
@@ -9,3 +9,3 @@ import build from 'estree-util-build-jsx' | ||
* @typedef RecmaJsxBuildOptions | ||
* @property {boolean} [_contain] Semihidden option which here results in getting the automatic runtime from `arguments[0]` instead of importing it | ||
* @property {'program' | 'function-body'} [outputFormat='program'] Whether to keep the import of the automatic runtime or get it from `arguments[0]` instead | ||
*/ | ||
@@ -20,3 +20,3 @@ | ||
export function recmaJsxBuild(options = {}) { | ||
var {_contain} = options | ||
var {outputFormat} = options | ||
@@ -31,6 +31,7 @@ return transform | ||
// In contain mode, replace the import that was just generated, and get | ||
// `jsx`, `jsxs`, and `Fragment` from `arguments[0]` instead. | ||
// When compiling to a function body, replace the import that was just | ||
// generated, and get `jsx`, `jsxs`, and `Fragment` from `arguments[0]` | ||
// instead. | ||
if ( | ||
_contain && | ||
outputFormat === 'function-body' && | ||
tree.body[0] && | ||
@@ -37,0 +38,0 @@ tree.body[0].type === 'ImportDeclaration' && |
@@ -5,3 +5,3 @@ /** | ||
* @typedef RecmaJsxRewriteOptions | ||
* @property {boolean} [_contain] Semihidden option which here results in getting `useMDXComponents` from `arguments[0]` instead of importing it | ||
* @property {'program' | 'function-body'} [outputFormat='program'] Whether to use an import statement or `arguments[0]` to get the provider | ||
* @property {string} [providerImportSource] Place to import a provider from | ||
@@ -22,5 +22,5 @@ */ | ||
/** | ||
* Semihidden option which here results in getting `useMDXComponents` from `arguments[0]` instead of importing it | ||
* Whether to use an import statement or `arguments[0]` to get the provider | ||
*/ | ||
_contain?: boolean; | ||
outputFormat?: 'program' | 'function-body'; | ||
/** | ||
@@ -27,0 +27,0 @@ * Place to import a provider from |
@@ -1,2 +0,2 @@ | ||
import isIdentifierName from 'estree-util-is-identifier-name' | ||
import {name as isIdentifierName} from 'estree-util-is-identifier-name' | ||
import {walk} from 'estree-walker' | ||
@@ -11,3 +11,3 @@ import {analyze} from 'periscopic' | ||
* @typedef RecmaJsxRewriteOptions | ||
* @property {boolean} [_contain] Semihidden option which here results in getting `useMDXComponents` from `arguments[0]` instead of importing it | ||
* @property {'program' | 'function-body'} [outputFormat='program'] Whether to use an import statement or `arguments[0]` to get the provider | ||
* @property {string} [providerImportSource] Place to import a provider from | ||
@@ -26,3 +26,3 @@ */ | ||
export function recmaJsxRewrite(options = {}) { | ||
var {providerImportSource, _contain} = options | ||
var {providerImportSource, outputFormat} = options | ||
return transform | ||
@@ -50,4 +50,6 @@ | ||
if (importProvider) { | ||
// @ts-ignore to do: figure out why `'init'` is not a string? | ||
tree.body.unshift(createImportProvider(providerImportSource, _contain)) | ||
tree.body.unshift( | ||
// @ts-ignore to do: figure out why `'init'` is not a string? | ||
createImportProvider(providerImportSource, outputFormat) | ||
) | ||
} | ||
@@ -91,6 +93,3 @@ | ||
// But `foo` and `b-ar` are tag names. | ||
else if ( | ||
isIdentifierName.name(name.name) && | ||
!/^[a-z]/.test(name.name) | ||
) { | ||
else if (isIdentifierName(name.name) && !/^[a-z]/.test(name.name)) { | ||
if ( | ||
@@ -304,3 +303,3 @@ !scope.components.includes(name.name) && | ||
function createImportProvider(providerImportSource, contain) { | ||
function createImportProvider(providerImportSource, outputFormat) { | ||
var specifiers = [ | ||
@@ -313,3 +312,3 @@ u('ImportSpecifier', { | ||
if (contain) { | ||
if (outputFormat === 'function-body') { | ||
return u('VariableDeclaration', { | ||
@@ -316,0 +315,0 @@ kind: 'const', |
@@ -25,2 +25,9 @@ import visit from 'unist-util-visit' | ||
/** | ||
* | ||
* @param {import('unist').Parent} node | ||
* @param {number} index | ||
* @param {import('unist').Parent} parent | ||
* @returns | ||
*/ | ||
function onvisit(node, index, parent) { | ||
@@ -27,0 +34,0 @@ var data |
@@ -22,2 +22,9 @@ import u from 'unist-builder' | ||
: u('Identifier', {name: 'default'}) | ||
var value = specifier.local | ||
if (specifier.type === 'ExportSpecifier') { | ||
value = key | ||
key = specifier.local | ||
} | ||
return create( | ||
@@ -27,7 +34,7 @@ specifier, | ||
kind: 'init', | ||
shorthand: key.name === specifier.local.name, | ||
shorthand: key.name === value.name, | ||
method: false, | ||
computed: false, | ||
key, | ||
value: specifier.local | ||
value | ||
}) | ||
@@ -34,0 +41,0 @@ ) |
@@ -10,8 +10,5 @@ /** | ||
* | ||
* @typedef {Omit<ProcessorOptions, 'jsx' | 'jsxImportSource' | 'jsxRuntime' | 'pragma' | 'pragmaFrag' | 'pragmaImportSource' | 'providerImportSource' | '_contain'> } EvaluateProcessorOptions | ||
* @typedef {Omit<ProcessorOptions, 'jsx' | 'jsxImportSource' | 'jsxRuntime' | 'pragma' | 'pragmaFrag' | 'pragmaImportSource' | 'providerImportSource' | 'outputFormat'> } EvaluateProcessorOptions | ||
* | ||
* @typedef ExtraOptions | ||
* @property {string} baseUrl URL to resolve imports from (typically: pass `import.meta.url`) | ||
* | ||
* @typedef {EvaluateProcessorOptions & RunnerOptions & ExtraOptions} EvaluateOptions | ||
* @typedef {EvaluateProcessorOptions & RunnerOptions} EvaluateOptions | ||
*/ | ||
@@ -47,9 +44,3 @@ /** | ||
}; | ||
export type EvaluateProcessorOptions = Omit<ProcessorOptions, 'jsx' | 'jsxImportSource' | 'jsxRuntime' | 'pragma' | 'pragmaFrag' | 'pragmaImportSource' | 'providerImportSource' | '_contain'>; | ||
export type ExtraOptions = { | ||
/** | ||
* URL to resolve imports from (typically: pass `import.meta.url`) | ||
*/ | ||
baseUrl: string; | ||
}; | ||
export type EvaluateOptions = EvaluateProcessorOptions & RunnerOptions & ExtraOptions; | ||
export type EvaluateProcessorOptions = Omit<ProcessorOptions, 'jsx' | 'jsxImportSource' | 'jsxRuntime' | 'pragma' | 'pragmaFrag' | 'pragmaImportSource' | 'providerImportSource' | 'outputFormat'>; | ||
export type EvaluateOptions = EvaluateProcessorOptions & RunnerOptions; |
@@ -10,8 +10,5 @@ /** | ||
* | ||
* @typedef {Omit<ProcessorOptions, 'jsx' | 'jsxImportSource' | 'jsxRuntime' | 'pragma' | 'pragmaFrag' | 'pragmaImportSource' | 'providerImportSource' | '_contain'> } EvaluateProcessorOptions | ||
* @typedef {Omit<ProcessorOptions, 'jsx' | 'jsxImportSource' | 'jsxRuntime' | 'pragma' | 'pragmaFrag' | 'pragmaImportSource' | 'providerImportSource' | 'outputFormat'> } EvaluateProcessorOptions | ||
* | ||
* @typedef ExtraOptions | ||
* @property {string} baseUrl URL to resolve imports from (typically: pass `import.meta.url`) | ||
* | ||
* @typedef {EvaluateProcessorOptions & RunnerOptions & ExtraOptions} EvaluateOptions | ||
* @typedef {EvaluateProcessorOptions & RunnerOptions} EvaluateOptions | ||
*/ | ||
@@ -35,3 +32,3 @@ | ||
...rest, | ||
_contain: true, | ||
outputFormat: 'function-body', | ||
providerImportSource: useMDXComponents ? '#' : undefined | ||
@@ -38,0 +35,0 @@ }, |
/** | ||
* Create a file and options from a given `vfileCompatible` and options that | ||
* might container `format: 'detect'`. | ||
* might contain `format: 'detect'`. | ||
* | ||
@@ -5,0 +5,0 @@ * @param {import('vfile').VFileCompatible} vfileCompatible |
@@ -6,3 +6,3 @@ import vfile from 'vfile' | ||
* Create a file and options from a given `vfileCompatible` and options that | ||
* might container `format: 'detect'`. | ||
* might contain `format: 'detect'`. | ||
* | ||
@@ -9,0 +9,0 @@ * @param {import('vfile').VFileCompatible} vfileCompatible |
{ | ||
"name": "xdm", | ||
"version": "1.5.1", | ||
"version": "1.6.0", | ||
"description": "an MDX compiler", | ||
@@ -43,3 +43,3 @@ "license": "MIT", | ||
"estree-util-build-jsx": "^1.1.0", | ||
"estree-util-is-identifier-name": "^1.0.0", | ||
"estree-util-is-identifier-name": "^2.0.0", | ||
"estree-walker": "^3.0.0", | ||
@@ -66,3 +66,3 @@ "hast-util-to-estree": "^1.2.0", | ||
"@emotion/react": "^11.0.0", | ||
"@mdx-js/react": "^2.0.0-next.8", | ||
"@mdx-js/react": "2.0.0-next.8", | ||
"@theme-ui/preset-base": "^0.3.0", | ||
@@ -101,2 +101,3 @@ "@types/babel__core": "^7.0.0", | ||
"theme-ui": "^0.3.0", | ||
"type-coverage": "^2.0.0", | ||
"typescript": "^4.0.0", | ||
@@ -112,3 +113,3 @@ "vue": "^3.0.0", | ||
"prebuild": "rimraf \"*.d.ts\" \"{lib,test}/**/*.d.ts\"", | ||
"build": "tsc", | ||
"build": "tsc && type-coverage", | ||
"test": "npm run format && npm run test-coverage && npm run build", | ||
@@ -197,3 +198,8 @@ "prepublishOnly": "npm run build" | ||
] | ||
}, | ||
"typeCoverage": { | ||
"atLeast": 80, | ||
"detail": true, | ||
"strict": true | ||
} | ||
} |
459
readme.md
@@ -8,3 +8,3 @@ # xdm | ||
**xdm** is an MDX compiler that focussed on two things: | ||
**xdm** is an MDX compiler that focusses on two things: | ||
@@ -17,3 +17,4 @@ 1. Compiling the MDX syntax (markdown + JSX) to JavaScript | ||
maps, ESM only, defaulting to an automatic JSX runtime, no Babel, smallish | ||
browser size, more docs, esbuild and Rollup plugins). | ||
browser size, more docs, import/exports in evaluate, esbuild and Rollup | ||
plugins). | ||
@@ -45,2 +46,5 @@ There are also some cool experimental features in [👩🔬 Lab][lab]! | ||
Note: if you’re in CommonJS, you *can* use this, but with a dynamic import | ||
expression: `var xdm = await import('xdm')`. | ||
## Contents | ||
@@ -59,3 +63,2 @@ | ||
* [Requiring `.mdx` files directly](#requiring-mdx-files-directly) | ||
* [Imports in `evaluate`](#imports-in-evaluate) | ||
* [MDX syntax](#mdx-syntax) | ||
@@ -100,3 +103,3 @@ * [Markdown](#markdown) | ||
Sometimes the term is used for a runtime/helper library. | ||
**xdm** has **no runtime**. | ||
**xdm** has **no runtime**: it’s not needed! | ||
@@ -131,3 +134,3 @@ Most often the term is used for the format: markdown + JS(X) (there are some | ||
```mdx | ||
export const Thing = () => <>World!</> | ||
export var Thing = () => <>World!</> | ||
@@ -143,3 +146,3 @@ # Hello, <Thing /> | ||
export const Thing = () => <>World!</> | ||
export var Thing = () => <>World!</> | ||
@@ -158,6 +161,5 @@ export default function MDXContent() { | ||
*** | ||
Now for how to get the actual output. | ||
Add some code in `example.js` to compile `example.mdx` to JavaScript: | ||
To compile `example.mdx` add some code in `example.js`: | ||
```js | ||
@@ -175,3 +177,3 @@ import fs from 'fs/promises' | ||
Now, the *actual* output of running `node example.js` is: | ||
The *actual* output of running `node example.js` is: | ||
@@ -182,3 +184,3 @@ ```js | ||
export const Thing = () => _jsx(_Fragment, {children: 'World!'}) | ||
export var Thing = () => _jsx(_Fragment, {children: 'World!'}) | ||
@@ -203,3 +205,3 @@ function MDXContent(_props) { | ||
* JSX is compiled away to function calls and an import of React | ||
* JSX is compiled away to function calls and an import of React† | ||
* The content component can be given `{components: {h1: MyComponent}}` to use | ||
@@ -210,2 +212,6 @@ something else for the heading | ||
† **xdm** is not coupled to React. | ||
You can also use it with [Preact](#preact), [Vue](#vue), [Emotion](#emotion), | ||
[Theme UI](#theme-ui), etc. | ||
See [§ MDX content][mdx-content] below on how to use the result. | ||
@@ -301,6 +307,18 @@ | ||
List of recma plugins, presets, and pairs. | ||
List of recma plugins. | ||
This is a new ecosystem, currently in beta, to transform | ||
[esast](https://github.com/syntax-tree/esast) (JavaScript) trees. | ||
[esast](https://github.com/syntax-tree/esast) trees (JavaScript). | ||
###### `options.mdExtensions` | ||
List of markdown extensions, with dot (`string[]`, default: `['.md', | ||
'.markdown', '.mdown', '.mkdn', '.mkd', '.mdwn', '.mkdown', '.ron']`). | ||
###### `options.mdxExtensions` | ||
List of MDX extensions, with dot (`string[]`, default: `['.mdx']`). | ||
Has no effect in `compile` or `evaluate`, but does affect [esbuild][], | ||
[Rollup][], and the experimental ESM loader + register hook (see [👩🔬 | ||
Lab][lab]). | ||
###### `options.format` | ||
@@ -342,14 +360,145 @@ | ||
###### `options.mdExtensions` | ||
###### `options.outputFormat` | ||
List of markdown extensions, with dot (`Array.<string>`, default: `['.md', | ||
'.markdown', '.mdown', '.mkdn', '.mkd', '.mdwn', '.mkdown', '.ron']`). | ||
Output format to generate (`'program' | 'function-body'`, default: `'program'`). | ||
In most cases `'program'` should be used, as it results in a whole program. | ||
Internally, [`evaluate`][eval] uses `outputFormat: 'function-body'` to compile | ||
to code that can be `eval`ed. | ||
In some cases, you might want to do what `evaluate` does in separate steps | ||
yourself, such as when compiling on the server and running on the client. | ||
###### `options.mdxExtensions` | ||
The `'program'` format will use import statements to import the runtime (and | ||
optionally provider) and use an export statement to yield the `MDXContent` | ||
component. | ||
List of MDX extensions, with dot (`Array.<string>`, default: `['.mdx']`). | ||
Has no effect in `compile` or `evaluate`, but does affect [esbuild][], | ||
[Rollup][], and the experimental ESM loader + register hook (see [👩🔬 | ||
Lab][lab]). | ||
The `'function-body'` format will get the runtime (and optionally provider) from | ||
`arguments[0]`, rewrite export statements, and use a return statement to yield | ||
what was exported. | ||
Normally, this output format will throw on `import` (and `export … from`) | ||
statements, but you can support them by setting | ||
[`options.useDynamicImport`][usedynamicimport]. | ||
<details> | ||
<summary>Example</summary> | ||
A module `example.js`: | ||
```js | ||
import {compile} from 'xdm' | ||
main('export var no = 3.14\n\n# hi {no}') | ||
async function main(code) { | ||
console.log(String(await compile(code, {outputFormat: 'program'}))) // Default | ||
console.log(String(await compile(code, {outputFormat: 'function-body'}))) | ||
} | ||
``` | ||
…yields: | ||
```js | ||
import {Fragment as _Fragment, jsx as _jsx} from 'react/jsx-runtime' | ||
export var no = 3.14 | ||
function MDXContent(_props) { /* … */ } | ||
export default MDXContent | ||
``` | ||
```js | ||
const {Fragment: _Fragment, jsx: _jsx} = arguments[0] | ||
var no = 3.14 | ||
function MDXContent(_props) { /* … */ } | ||
return {no, default: MDXContent} | ||
``` | ||
</details> | ||
###### `options.useDynamicImport` | ||
Whether to compile to dynamic import expressions (`boolean`, default: `false`). | ||
This option applies when [`options.outputFormat`][outputformat] is | ||
`'function-body'`. | ||
**xdm** can turn import statements (`import x from 'y'`) into dynamic imports | ||
(`const {x} = await import('y')`). | ||
This is useful because import statements only work at the top level of | ||
JavaScript modules, whereas `import()` is available inside function bodies. | ||
When you turn `useDynamicImport` on, you should probably set [`options.baseUrl`][baseurl] too. | ||
<details> | ||
<summary>Example</summary> | ||
Say we have a couple modules: | ||
```js | ||
// meta.js: | ||
export var title = 'World' | ||
// numbers.js: | ||
export var no = 3.14 | ||
// example.js: | ||
import {compileSync} from 'xdm' | ||
var code = `import {name} from './meta.js' | ||
export {no} from './numbers.js' | ||
# hi {name}!` | ||
console.log(String(compileSync(code, {outputFormat: 'function-body', useDynamicImport: true}))) | ||
``` | ||
…now running `node example.js` yields: | ||
```js | ||
const {Fragment: _Fragment, jsx: _jsx, jsxs: _jsxs} = arguments[0] | ||
const {name} = await import('./meta.js') | ||
const {no} = await import('./numbers.js') | ||
function MDXContent(_props) { /* … */ } | ||
return {no, default: MDXContent} | ||
``` | ||
</details> | ||
###### `options.baseUrl` | ||
Resolve relative `import` (and `export … from`) from this URL (`string?`, | ||
example: `import.meta.url`). | ||
Relative specifiers are non-absolute URLs that start with `/`, `./`, or `../`. | ||
For example: `/index.js`, `./folder/file.js`, or `../main.js`. | ||
This option is useful when code will run in a different place. | ||
One example is when `.mdx` files are in path *a* but compiled to path *b* and | ||
imports should run relative the path *b*. | ||
Another example is when evaluating code, whether in Node or a browser. | ||
<details> | ||
<summary>Example</summary> | ||
Say we have a module `example.js`: | ||
```js | ||
import {compile} from 'xdm' | ||
main() | ||
async function main() { | ||
var code = 'export {number} from "./data.js"\n\n# hi' | ||
var baseUrl = 'https://a.full/url' // Typically `import.meta.url` | ||
console.log(String(await compile(code, {baseUrl}))) | ||
} | ||
``` | ||
…now running `node example.js` yields: | ||
```js | ||
import {Fragment as _Fragment, jsx as _jsx} from 'react/jsx-runtime' | ||
export {number} from 'https://a.full/data.js' | ||
function MDXContent(_props) { /* … */ } | ||
export default MDXContent | ||
``` | ||
</details> | ||
###### `options.SourceMapGenerator` | ||
@@ -420,3 +569,3 @@ | ||
export const Thing = () => React.createElement(React.Fragment, null, 'World!') | ||
export var Thing = () => React.createElement(React.Fragment, null, 'World!') | ||
@@ -454,4 +603,4 @@ function MDXContent(_props) { | ||
- | ||
-export const Thing = () => React.createElement(React.Fragment, null, 'World!') | ||
+export const Thing = () => <>World!</> | ||
-export var Thing = () => React.createElement(React.Fragment, null, 'World!') | ||
+export var Thing = () => <>World!</> | ||
@@ -478,4 +627,3 @@ function MDXContent(_props) { | ||
JSX runtime to use (`string`, `'automatic'` or `'classic'`, default: | ||
`'automatic'`). | ||
JSX runtime to use (`'automatic' | 'classic'`, default: `'automatic'`). | ||
The classic runtime compiles to calls such as `h('p')`, the automatic runtime | ||
@@ -501,4 +649,4 @@ compiles to `import _jsx from '$importSource/jsx-runtime'\n_jsx('p')`. | ||
-export const Thing = () => _jsx(_Fragment, {children: 'World!'}) | ||
+export const Thing = () => React.createElement(React.Fragment, null, 'World!') | ||
-export var Thing = () => _jsx(_Fragment, {children: 'World!'}) | ||
+export var Thing = () => React.createElement(React.Fragment, null, 'World!') | ||
… | ||
@@ -566,4 +714,4 @@ ``` | ||
-export const Thing = () => React.createElement(React.Fragment, null, 'World!') | ||
+export const Thing = () => preact.createElement(preact.Fragment, null, 'World!') | ||
-export var Thing = () => React.createElement(React.Fragment, null, 'World!') | ||
+export var Thing = () => preact.createElement(preact.Fragment, null, 'World!') | ||
… | ||
@@ -629,6 +777,6 @@ ``` | ||
Run MDX. | ||
It’s called **evaluate** because it `eval`s JavaScript. | ||
To get the full power of MDX it’s suggested to use `compile`, write to a file, | ||
and then run with Node or bundle with [esbuild][]/[Rollup][]/[webpack][]. | ||
Compile and run MDX. | ||
☢️ It’s called **evaluate** because it `eval`s JavaScript. | ||
When possible, please use `compile`, write to a file, and then run with Node or | ||
bundle with [esbuild][]/[Rollup][]/[webpack][]. | ||
But if you trust your content, `evaluate` can work. | ||
@@ -640,5 +788,5 @@ | ||
Typically, `import` (or `export … from`) does not work. | ||
But there is experimental support if a `baseUrl` is passed. | ||
See [Imports in `evaluate`](#imports-in-evaluate) in [👩🔬 Lab][lab]! | ||
Typically, `import` (or `export … from`) do not work here. | ||
They can be compiled to dynamic `import()` by passing | ||
[`options.useDynamicImport`][usedynamicimport]. | ||
@@ -656,3 +804,3 @@ ###### `file` | ||
* `jsx*` and `pragma*` options are replaced by `jsx`, `jsxs`, and `Fragment` | ||
* `baseUrl` is an experiment that only works here | ||
* `outputFormat` is set to `function-body` | ||
@@ -695,11 +843,2 @@ ###### `options.jsx` | ||
###### `options.baseUrl` | ||
Where to resolve relative imports from (`string?`, example: `import.meta.url`). | ||
If one is set, `import` (and `export … from`) can be handled. | ||
Otherwise, they crash. | ||
Experimental! | ||
See [Imports in `evaluate`](#imports-in-evaluate) in [👩🔬 Lab][lab]! | ||
###### Returns | ||
@@ -733,3 +872,3 @@ | ||
Run MDX. | ||
Compile and run MDX. | ||
Synchronous version of [`evaluate`][eval]. | ||
@@ -754,6 +893,5 @@ When possible please use the async `evaluate`. | ||
[ESM loaders](https://nodejs.org/api/esm.html#esm_loaders) are a very | ||
experimental feature in Node, slated to change. | ||
Still, the feature lets projects “hijack” imports, to do all sorts of fancy | ||
things! | ||
[ESM loaders](https://nodejs.org/api/esm.html#esm_loaders) are an experimental | ||
feature in Node, slated to change. | ||
Still, they let projects “hijack” imports, to do all sorts of fancy things! | ||
**xdm** comes with experimental support for importing `.mdx` files with | ||
@@ -805,4 +943,3 @@ on-the-fly compilation, using `xdm/esm-loader.js`: | ||
is a deprecated feature in Node. | ||
Still, the feature lets projects “hijack” `require` calls, to do all sorts of | ||
fancy things! | ||
Still, it lets projects “hijack” `require` calls to do fancy things. | ||
**xdm** comes with support for requiring `.mdx` files with on-the-fly | ||
@@ -850,54 +987,2 @@ evaluation, using `xdm/register.cjs`: | ||
### Imports in `evaluate` | ||
`evaluate` wraps code in an [`AsyncFunction`][async-function], which means that | ||
it also supports top-level await. | ||
So, as a follow up, **xdm** can turn import statements (`import {x} from 'y'`) | ||
into import expressions (`const {x} = await import('y')`). | ||
There’s one catch: where to import from? | ||
You must pass a `baseUrl` to [`evaluate`][eval]. | ||
Typically, you should use `import.meta.url`. | ||
Say we have a module `data.js`: | ||
```js | ||
export var number = 3.14 | ||
``` | ||
And our module `example.js` looks as follows: | ||
```js | ||
import * as runtime from 'react/jsx-runtime.js' | ||
import {evaluate} from 'xdm' | ||
main() | ||
async function main() { | ||
var baseUrl = 'https://a.full/url' // Typically `import.meta.url` | ||
var {number} = await evaluate( | ||
'export {number} from "./data.js"', | ||
{...runtime, baseUrl} | ||
) | ||
console.log(number) | ||
} | ||
``` | ||
<details> | ||
<summary>Show ± evaluated JS</summary> | ||
```js | ||
;(async function (_runtime) { | ||
const {number} = await import('https://a.full/data.js') | ||
function MDXContent(_props) { /* … */ } | ||
return {number, default: MDXContent} | ||
})(runtime) | ||
``` | ||
</details> | ||
## MDX syntax | ||
@@ -1045,3 +1130,3 @@ | ||
export const Local = props => <span style={{color: 'red'}} {...props} /> | ||
export var Local = props => <span style={{color: 'red'}} {...props} /> | ||
@@ -1056,3 +1141,3 @@ An <External /> component and <Local>a local component</Local>. | ||
import data from './population.js' | ||
export const pi = 3.14 | ||
export var pi = 3.14 | ||
@@ -1062,9 +1147,7 @@ <MyChart data={data} label={'Something with ' + pi} /> | ||
Note that when using [`evaluate`][eval], `import`s are not supported but | ||
`export`s can still be used to define things in MDX (except `export … from`, | ||
which also imports). | ||
Typically, `import` (or `export … from`) do not work here. | ||
They can be compiled to dynamic `import()` by passing | ||
[`options.useDynamicImport`][usedynamicimport]. | ||
> There is experimental support for `import` (and `export … from`) in `evaluate` | ||
> if a `baseUrl` is passed. | ||
> See [Imports in `evaluate`](#imports-in-evaluate) in [👩🔬 Lab][lab]! | ||
`export`s do work and they are returned. | ||
@@ -1076,3 +1159,3 @@ ### Expressions | ||
```mdx | ||
export const pi = 3.14 | ||
export var pi = 3.14 | ||
@@ -1090,2 +1173,5 @@ Two 🍰 is: {pi * 2} | ||
All content (headings, paragraphs, etc) you write are exported as the default | ||
export from a compiled MDX file as a component. | ||
It’s possible to pass components in. | ||
@@ -1101,3 +1187,3 @@ Say we have a `message.mdx` file: | ||
```js | ||
import Message from './message.mdx' // Assumes a bundler is used to compile MDX -> JS. | ||
import Message from './message.mdx' // Assumes an integration is used to compile MDX -> JS. | ||
@@ -1194,2 +1280,7 @@ <Message components={{Planet: () => 'Venus'}} /> | ||
esbuild takes care of turning modern JavaScript features into syntax that works | ||
wherever you want it to. | ||
No Babel needed. | ||
See esbuild’s docs for more info. | ||
#### Rollup | ||
@@ -1244,3 +1335,3 @@ | ||
List of [`picomatch`][pico] patterns to include and/or exclude | ||
(`string`, `RegExp`, `Array.<string|RegExp>`, default: `[]`). | ||
(`string`, `RegExp`, `(string|RegExp)[]`, default: `[]`). | ||
@@ -1289,3 +1380,3 @@ #### Webpack | ||
See | ||
[#11](https://github.com/wooorm/xdm/issues/11#issuecomment-785043772) for | ||
[GH-11](https://github.com/wooorm/xdm/issues/11#issuecomment-785043772) for | ||
details. | ||
@@ -1404,3 +1495,3 @@ | ||
```mdx | ||
export const Box = () => ( | ||
export var Box = () => ( | ||
<div style={{padding: 20, backgroundColor: 'tomato'}} /> | ||
@@ -1444,5 +1535,8 @@ ) | ||
module.exports = { | ||
// Support MDX files as pages: | ||
pageExtensions: ['mdx', 'tsx', 'ts', 'jsx', 'js'], | ||
// Support loading `.mdx`: | ||
webpack(config) { | ||
config.module.rules.push({ | ||
test: /\.mdx/, | ||
test: /\.mdx$/, | ||
use: [{loader: 'xdm/webpack.cjs', options: {}}] | ||
@@ -1462,6 +1556,7 @@ }) | ||
> What about React server components? | ||
> While they are currently very alpha, and not shipping soon, there is an | ||
> What about **React server components**? | ||
> | ||
> While they are currently alpha and not shipping soon, there is an | ||
> [experimental demo](https://wooorm.com/server-components-mdx-demo/) | ||
> combining xdm with RSC. | ||
> combining **xdm** with RSC. | ||
@@ -1473,3 +1568,3 @@ You can set `providerImportSource` to `'@mdx-js/react'` (which has to be | ||
import {MDXProvider} from '@mdx-js/react' | ||
import Post from './post.mdx' // Assumes a bundler is used to compile MDX -> JS. | ||
import Post from './post.mdx' // Assumes an integration is used to compile MDX -> JS. | ||
@@ -1552,3 +1647,3 @@ <MDXProvider components={{em: props => <i {...props} />}}> | ||
import {components, ThemeProvider} from 'theme-ui' | ||
import Post from './post.mdx' // Assumes a bundler is used to compile MDX -> JS. | ||
import Post from './post.mdx' // Assumes an integration is used to compile MDX -> JS. | ||
@@ -1716,3 +1811,3 @@ <ThemeProvider theme={base}> | ||
import SyntaxHighlighter from 'react-syntax-highlighter' | ||
import Post from './example.mdx' // Assumes a bundler is used to compile MDX -> JS. | ||
import Post from './example.mdx' // Assumes an integration is used to compile MDX -> JS. | ||
@@ -1943,4 +2038,4 @@ <Post components={{code}} /> | ||
```mdx | ||
export const name = 'World' | ||
export const title = 'Hi, ' + name + '!' | ||
export var name = 'World' | ||
export var title = 'Hi, ' + name + '!' | ||
@@ -1953,3 +2048,3 @@ # {title} | ||
```js | ||
import Post from './post.mdx' // Assumes a bundler is used to compile MDX -> JS. | ||
import Post from './post.mdx' // Assumes an integration is used to compile MDX -> JS. | ||
@@ -2018,93 +2113,6 @@ console.log(Post.title) // Prints 'Hi, World!' | ||
> **Note**: [`remark-mdx-frontmatter`](https://github.com/remcohaszing/remark-mdx-frontmatter) | ||
> implements the following (and a bit more!) | ||
That’s what | ||
[`remark-mdx-frontmatter`](https://github.com/remcohaszing/remark-mdx-frontmatter) | ||
does. | ||
We can write a remark plugin which turns the YAML frontmatter into an ESM export | ||
to solve it: | ||
```js | ||
import fs from 'fs/promises' | ||
import yaml from 'js-yaml' | ||
import remarkFrontmatter from 'remark-frontmatter' | ||
import visit from 'unist-util-visit' | ||
import {compile} from 'xdm' | ||
main() | ||
async function main() { | ||
console.log( | ||
await compile(await fs.readFile('example.mdx'), { | ||
remarkPlugins: [remarkFrontmatter, remarkMdxExportYaml] | ||
}) | ||
) | ||
} | ||
function remarkMdxExportYaml() { | ||
return (tree) => { | ||
// Find all YAML nodes. | ||
visit(tree, 'yaml', onyaml) | ||
} | ||
} | ||
function onyaml(node, index, parent) { | ||
// Create an estree from the YAML, that looks like: | ||
// `export const frontmatter = JSON.parse("{…}")` | ||
// It looks a bit complex. | ||
// I like using astexplorer (set to JavaScript and espree) to figure out how | ||
// these things should look. | ||
var estree = { | ||
type: 'Program', | ||
body: [ | ||
{ | ||
type: 'ExportNamedDeclaration', | ||
declaration: { | ||
type: 'VariableDeclaration', | ||
kind: 'const', | ||
declarations: [ | ||
{ | ||
type: 'VariableDeclarator', | ||
id: {type: 'Identifier', name: 'frontmatter'}, | ||
init: { | ||
type: 'CallExpression', | ||
callee: { | ||
type: 'MemberExpression', | ||
object: {type: 'Identifier', name: 'JSON'}, | ||
property: {type: 'Identifier', name: 'parse'} | ||
}, | ||
arguments: [ | ||
{ | ||
type: 'Literal', | ||
value: JSON.stringify(yaml.load(node.value)) | ||
} | ||
] | ||
} | ||
} | ||
] | ||
} | ||
} | ||
] | ||
} | ||
// Replace the YAML node with MDX ESM. | ||
// We’re still in markdown, but by defining `data.estree`, we tell xdm to use | ||
// that when we’re in JavaScript! | ||
parent.children[index] = {type: 'mdxjsEsm', value: '', data: {estree}} | ||
} | ||
``` | ||
<details> | ||
<summary>Show equivalent JS</summary> | ||
```js | ||
export const frontmatter = JSON.parse('{"title":"Hi, World!"}') | ||
function MDXContent() { | ||
return <h1>{frontmatter.title}</h1> | ||
} | ||
export default MDXContent | ||
``` | ||
</details> | ||
## Plugins | ||
@@ -2151,2 +2159,4 @@ | ||
* `export`s work in `evaluate` | ||
* Add support for compiling import statements to dynamic import expressions | ||
* Add support for resolving import/export sources | ||
@@ -2169,3 +2179,3 @@ **Input**: | ||
instead) | ||
* Exported things are available from `evaluate` | ||
* Support for import and exports in `evaluate` | ||
* Fix a bug with encoding `"` in attributes | ||
@@ -2177,3 +2187,2 @@ | ||
* Add support for `require('./file.mdx')` in Node | ||
* Add support for imports in `evaluate` | ||
@@ -2347,2 +2356,8 @@ ## Architecture | ||
[outputformat]: #optionsoutputformat | ||
[baseurl]: #optionsbaseurl | ||
[usedynamicimport]: #optionsusedynamicimport | ||
[sm]: #optionssourcemapgenerator | ||
@@ -2349,0 +2364,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
143099
2125
2333
42
+ Addedestree-util-is-identifier-name@2.1.0(transitive)