-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor or remove MDX on demand #2606
Comments
Why? With export specifiers, people can put different things in packages?
What? Which types?
MDX has several options, it also has many uses, I don’t think it is a goal to have few options. There used to be an
Which plugins deal with that? How?
Bugs can be fixed.
Feature?
How do you mean discourage, if people still use it, just further down the line?
Which bugs?
What is broken?
Is that possible?
I believe that your proposal still needs a base URL, just in a plugin instead of in core?
Can you please specify which special handling you mean? I don’t understand how JSX is handled differently in
I... How? How does what you propose improve teaching?
I do not follow you. Which compat, with what? What is impossible now, what would be possible by removing code from core? Did you know that it is possible to load modules with blobs. We’d still need to deal with the imports, but: const example = `import {jsx as _jsx} from "react/jsx-runtime";
function _createMdxContent(props) {
const _components = {
h1: "h1",
...props.components
};
return _jsx(_components.h1, {
children: "Hello, world!"
});
}
export default function MDXContent(props = {}) {
const {wrapper: MDXLayout} = props.components || ({});
return MDXLayout ? _jsx(MDXLayout, {
...props,
children: _jsx(_createMdxContent, {
...props
})
}) : _createMdxContent(props);
}`
const blob = new Blob([example], { type: 'text/javascript' })
const url = URL.createObjectURL(blob)
import(url).then(console.log, console.error).then(clean)
function clean() { URL.revokeObjectURL(url) }
Using
|
These are supposed to run in different environments. Compiling typically happens at build time. In case of MDX on demand this happens at runtime on the server, which is also fine. Evaluating typically happens on the frontend. IMO this also goes against the unified philosophy of keeping packages small and focused. MDX on demand can be external.
Dependencies on
I agree. However, I don’t think we need this option in core either.
The following plugin injects a statement that usually works, but becomes unreachable with the import { compile } from '@mdx-js/mdx'
function plugin() {
return (ast) => {
ast.body.push({
type: 'ExpressionStatement',
expression: { type: 'Identifier', name: 'unreachable' }
})
}
}
const file = await compile('', {
outputFormat: 'function-body',
recmaPlugins: [plugin]
})
console.log(String(file)) output: "use strict";
const {Fragment: _Fragment, jsx: _jsx} = arguments[0];
function _createMdxContent(props) {
return _jsx(_Fragment, {});
}
function MDXContent(props = {}) {
const {wrapper: MDXLayout} = props.components || ({});
return MDXLayout ? _jsx(MDXLayout, {
...props,
children: _jsx(_createMdxContent, {
...props
})
}) : _createMdxContent(props);
}
return {
default: MDXContent
};
unreachable;
They can be. I’m proposing to externalize this, so they won’t have to be.
Feature? Yes. Good abstraction? IMO not really. Definitely opiniated.
I’m proposing to make the user run the actual eval using JavaScript builtins. Don’t let MDX handle this.
These re-exports are all problematic. export const __proto__ = {}
export { a as 'b' }
export { a as 'b' } from 'c'
export * as a from 'b'
export { __proto__ } from 'a'
The broken re-exports I mentioned above, but also the code can be replaced with
My proposed solution for a backwards compatibility needs a base URL. My proposal is to eventually remove
JSX (automatic runtime) gets compiled to 2 things:
import { useState } from 'react'
<div /> becomes: import { useState } from 'react'
import { jsx as _jsx } from 'react/jsx-runtime'
_jsx('div', {}) With import { useState } from 'react'
const { jsx as _jsx } arguments[0]
_jsx('div', {})
Removing things never makes anything possible that were previously impossible. I’m saying I believe there are better solutions. Instead of having a special compiler option and MDX runtime code, I would like to:
From this const importName = '_import'
const file = await compile('Hello {props.name}!', {
recmaPlugins: [[recmaModuleToFunction, { importName }]]
})
const fn = new AsyncFunction(importName, String(file))
const { default: MDXContent } = (await fn(async (name) => {
if (name === 'react/jsx-runtime') {
return runtime
}
throw new Error(`Cannot find module ${name}`)
}))
const result = renderToStaticMarkup(<MDXContent name="React" />) All this example does with MDX, is compile it to JavaScript. It uses a recma plugin to turn the output into a function body. JSX doesn’t get special treatment. It’s just an import. The user can resolve any imports however they want. The examples in the readme show various example strategies. One example is basically a base URL that is compatible with bundlers. Another example shows how to use this with a CDN as a base URL.
This didn’t occur to me. It’s an interesting find. This is less flexible than const file = await compile('Hello {props.name}!', {
recmaPlugins: [[recmaEsmBaseUrl, { baseUrl: 'https://esm.sh' }]]
})
const blob = new Blob([String(file)], { type: 'text/javascript' })
const url = URL.createObjectURL(blob)
import(url) So again, this only needs a plugin and documentation. It’s simple enough for users to implement without the need for an option in MDX. |
That’s possible, that’s why
Common things are easy but uncommon things are possible. Perhaps it is not common to use Node loaders but it’s supposed to be possible to run MDX everywhere.
MDX is small and focused.
It can be solved as an issue? This issue seems to exist because recma is a thing now and you are writing recma plugins that are unrelated to MDX. This could also be marked as
Bugs need to be fixed, here or there.
If it was that easy, that could happen. But it was not, so
Exports as literals are new, not supported by
I think I mentioned this a while ago already, I think when you were working on that utility. That we could use it here, if it supported the things that are needed. Not sure but I was under the impression your work was not compatible with the code here.
There are more things involved with JSX, such as injecting comments. Also, a bunch of classic options. And
Before Your point here can also be inverted: nothing is impossible, so removing things does nothing.
It is interesting, though, I am not sure such power needs to be the default. The current setup works with import maps. It seems like a different concept/plugin: a) turn a module into a function body, b) dependency injection alternative to import maps. What does your plugin do with
Why is this extra flexibility in
Importantly, |
I’m not arguing about every option. I’m arguing mixing runtime logic into the core package seems out of place. This happens to be some exports and an option.
Yes, absolutely. This is merely an example of a not very obvious problem that arises because of the
recma plugins can add exports or other transformations. These may or may not be related to MDX. Some are more stable than others. But I think adding exports is a relatively simple one that typically shouldn’t cause problems.
I just don’t see it. I really don’t think the latter is simpler: const file = await compile('Hello {props.name}!', {
recmaPlugins: [recmaModuleToFunction]
})
const fn = new AsyncFunction(String(file))
const { default: MDXContent } = await fn() vs import * as runtime from 'https://esm.sh/react/jsx-runtime'
const file = await compile('Hello {props.name}!', {
outputFormat: 'function-body'
})
const fn = await run(String(file), {...runtime, baseUrl: import.meta.url})
I stumbled upon this recently, but I didn’t realize they were new.
It wasn’t made for MDX specifically, but it works with any ESTree, including the one that MDX produces. It just doesn’t treat JSX runtime imports different from other imports.
Yes. I proposed 3 solutions. One is to use
This is taken out of context. I’m not downplaying the complexities of compiling MDX or JSX. I’m highlighting differences between the two output formats.
Why was that? I wasn’t involved as much back then. I think because back then the MDX output still contained JSX and Babel was considered the go-to tool to compile JSX? The situation is much better now, even without
They are transformed into regular property accessors of
At the time I needed to resolve modules. Some modules were authored by the user in a virtual file system. Relative imports needed to resolve to local files. Bare import specifiers needed to resolve to packages on https://esm.sh. I think it wasn’t even used with MDX back then. It just happens to be a good fit.
I don’t think |
Initial checklist
Problem
I have been thinking about MDX on demand lately, and I see some quirks/problems
mdx-js/mdx
package contains code for compiling code. But it also contains runtime code for evaluating the generated code. This seems like an odd place. For example, this adds runtime types for the compiler.Current solutions
The user can use
run
/evaluate
. Alternatively they can userecma-module-to-function
.Proposed solutions
I have some solutions that we can implement partially or gradually.
recma-module-to-function
. I spotted some quirks for edge cases inrecma-module-to-function
which I’m still resolving, but those exist in the current MDX on demand implementation as well. Several of the now generated code for MDX on demand can be solved using a custom import implementation. This can be non-breaking. A naive untested implementation:evaluate
/run
into a new package@mdx-js/on-demand
. This means the core no longer needs the optionoutputFormat
or any runtime code.evaluate
/run
. We can still document an example like https://github.com/remcohaszing/recma-module-to-function#examples and keep the terminology MDX on demand to describe this pattern. Some (subjective) benefits:AsyncFunction
constructor. I could even make it compatible with theFunction
constructor. This will trigger the ESLint ruleno-new-func
for many users, which I believe to be a good thing.Option 3 is definitely a breaking change and would be semver major. Option 2 could be done earlier via a deprecated re-export, but IMO that’s not worth the effort until we do a semver major.
The text was updated successfully, but these errors were encountered: