mdx-js / mdx

Markdown for the component era
https://mdxjs.com
MIT License
17.76k stars 1.14k forks source link

Please do not remove `classic` from `jsxRuntime` #2547

Closed ClassicOldSong closed 1 month ago

ClassicOldSong commented 1 month ago

Initial checklist

Problem

There're frameworks that solve the problem React tried to solve with jsxFactory in different ways. The render function is passed to the render method returned by the component dynamically, thus eliminates the requirements for a jsxFactory or a pragmaImportSource to be imported globally. Removing classic support would break these frameworks from functioning with MDX.

For example, with rEFui, components return a render method that accepts a renderer:

/** @jsx R.c */
/** @jsxFrag R.f */

function MyComponent(props, ...chileren) {
    return (R) => (
        <h1>Hello World!</h1>
    )
}

which yields

function MyCompoent(props, ...children) {
    return (R) => R.c('h1', null, 'Hello World!')
}

This behavior mentioned above is supported in almost all JSX transpilers on the market, including esbuild, rollup and babel.

Solution

Keep classic option for jsxRuntime and allow specifying null to pragmaImportSource for not generating pragma imports.

Alternatives

None

wooorm commented 1 month ago

I have no clue why you post this as an issue.

ClassicOldSong commented 1 month ago

I think I have made my points clear: classic JSX transformation is still valid in some frameworks, no dedicated JSX transformers have plan to get rid of classic transformation and nor should here.

Classic transformations are not obsolete and have meanings in some scenarios beyond the new jsx-runtime convention.

ClassicOldSong commented 1 month ago

If you don't understand why I opened this issue, please check your website and there's

👉 Note: support for the classic runtime is deprecated and will likely be removed in the next major version.

written multiple times in https://mdxjs.com/packages/mdx

wooorm commented 1 month ago

I think you can update your framework to support the automatic runtime

ClassicOldSong commented 1 month ago

I provided the above example to show that classic is a required feature for the framework to work. The renderer can be swapped on runtime instead of build time by providing it dynamically. It's a framework feature, upgrade sounds like a downgrade in this case.

For a more complete example:

import { signal } from 'refui'
import { createDOMRenderer } from 'refui/dom'
import { createHTMLRenderer } from 'refui/html'
import { defaults } from 'refui/browser'

const DOMRenderer = createDOMRenderer(defaults)
const HTMLRenderer = createHTMLRenderer()

const App = () => {
    const count = signal(0)
    const increment = () => {
        count.value += 1
    }

    return (R) => (
        <>
            <h1>Hello, rEFui</h1>
            <button on:click={increment}>Click me! Count is {count}</button>
        </>
    )
}

// Render directly onto the DOM
DOMRenderer.render(document.body, App)

// Render to string
console.log(HTMLRenderer.serialize(HTMLRenderer.createElement(App))
wooorm commented 1 month ago

Please listen to what I say: you can add the automatic runtime to your framework. The automatic runtime can do the same thing as the classic runtime. It can also do “update functions”

ClassicOldSong commented 1 month ago

But by doing it via import { jsx } from 'some/jsx-runtime', the swap is global. Unless render to function body, the jsx method that changes will affect all currently rendered components that were rendered by a different renderer.

wooorm commented 1 month ago

How many different Rs are there? Why do you want many different Rs?

ClassicOldSong commented 1 month ago

There might be a lot in the future, the R can even render to physical hardwares/non-browser environments(out of scope but it really can, I have build specific runtime and hardware platform that is capable of doing this).

Currently there are two of them, the DOM one and the HTML one.

remcohaszing commented 1 month ago

The original issue do not do something came across pretty confusing, but I see where you’re coming from. I don’t think a major version for MDX is happening very soon, but I do think it’s good to discuss if / how / why we want to support the classic runtime before we just remove it.

The JSX pragma R.c and fragment R.f don’t come from imports, but are injected by a function. I see how this conflicts with the automatic runtime, given the automatic runtime generates imports (even if we pass around the automatic runtime via options already in some places). The classic runtime does not typically rely on imports.

I am a bit confused though, how are you using rEfui with MDX currently? MDX also generates an import when using the classic runtime, configured by the option pragmaImportSource.

wooorm commented 1 month ago

Currently there are two of them, the DOM one and the HTML one.

There might be a lot in the future

Why do they have to be different in the same file? How is there one component going to hardware and another going to I dunno a PDF? One to HTML another to DOM?

The renderer can be swapped on runtime instead of build time by providing it dynamically.

You can also do this with an automatic runtime. Let someone import setThing from your framework, let people call setThing. Inside _jsx, take thing.


This is a discussion. Issues are not for discussions. We have discussions for discussions.

ClassicOldSong commented 1 month ago

Why do they have to be different in the same file? How is there one component going to hardware and another going to I dunno a PDF? One to HTML another to DOM?

Like displaying on screen and also provide a downloadable rendered result, in this case PDF is a valid render target.

You can also do this with an automatic runtime. Let someone import setThing from your framework, let people call setThing. Inside _jsx, take thing.

I have said, in the case where multiple renderers all have something rendered, changing thing globally will break their ability to render anything more.

I agree to move to discussions.


I am a bit confused though, how are you using rEfui with MDX currently? MDX also generates an import when using the classic runtime, configured by the option pragmaImportSource.

I'm exploring about making a document site for our upcoming JS runtime, and I'd like to use my own framework to handle the generation. As I have experimented, I did use a jsx-runtime wrap to the existing classic render methods but that's way too hacky and is not very portable, as described above.

Working example: stackblitz

remcohaszing commented 1 month ago

It doesn’t look that hacky IMO. As far as I can tell, you don’t need to assign or export those module level variables. You just need the wrap function.

wooorm commented 1 month ago

Like displaying on screen and also provide a downloadable rendered result, in this case PDF is a valid render target.

The thing with React/Preact/etc is that they create a VDOM. A virtual DOM. They later render those virtual nodes. They can render to PDF. To RSS. To HTML. To native. To CLIs.

Frameworks can do this. With the same nodes. Mapping them to different things. Why do you not do this? Why do you not take one node, and map it to PDFs and HTML and other things?

way too hacky and is not very portable

Looks fine to me.

Except that you must not expose jsxDEV like that. In development, a different file is loaded. Expose jsx and jsxs and Fragment from one file. Expose jsxDEV and Fragment from another file.

ClassicOldSong commented 1 month ago

In this example,yes. But when rendered to a static site, it means I have to inject the wrap function and create a renderer manually in each generated page, or it's not instantly usable.

Why do you not do this? Why do you not take one node, and map it to PDFs and HTML and other things?

For performance. I use JSX like what Solid does but don't want to introduce too much complexity distinguishing between server build and client build, in order to keep the framework simple and portable.

wooorm commented 1 month ago

I don’t think switching at runtime, per component, is good for performance. If you want performance, use a specific HTML runtime. A specific RSS runtime. A specific PDF runtime. That’s fast.

The automatic runtime is an improvement for performance. I recommend you use it.

ClassicOldSong commented 1 month ago

If you want performance, use a specific HTML runtime. A specific RSS runtime. A specific PDF runtime. That’s fast.

For doing one-shot render, that's true, but when the render context is preserved and only little modification is needed for a new render, my approach is much faster since it doesn't need to construct everything from scratch every time.

wooorm commented 1 month ago

That’s what React and friends also optimize for. Diffing virtual DOMs. Only applying the patches

ClassicOldSong commented 1 month ago

I don't need the diffing phase, changes are applied directly and precisely on where they need to change.

wooorm commented 1 month ago

You just explained applying patches. Patches means diffs?

ClassicOldSong commented 1 month ago

There's no diff happening when rendering, that means there're no patches generated to be applied.

It works almost in the same way Solid does, with some differences in implementation details.

wooorm commented 1 month ago

For doing one-shot render, that's true, but when the render context is preserved and only little modification is needed for a new render, my approach is much faster since it doesn't need to construct everything from scratch every time.

I understand this as patches. And something is diffing there.

Regardless, say you call these “little modifications” something else. How do these “little modifications” happen to a string of static HTML? To a string of static XML in an RSS? To a PDF? Wouldn’t The little modifications only really work, usefully, with a live DOM? Of which there is probably only one?

Two separate apps are created in your example: https://github.com/mdx-js/mdx/issues/2547#issuecomment-2422246133. Only the DOM version has some live binding, recalculation thing.

ClassicOldSong commented 1 month ago

That's not true. All of them have live bindings, just the HTML app isn't assigned to a variable.

import { signal. nextTick } from 'refui'
import { createDOMRenderer } from 'refui/dom'
import { createHTMLRenderer } from 'refui/html'
import { defaults } from 'refui/browser'

const DOMRenderer = createDOMRenderer(defaults)
const HTMLRenderer = createHTMLRenderer()

const count = signal(0)

const App = () => {
    const increment = () => {
        count.value += 1
    }

    return (R) => (
        <>
            <h1>Hello, rEFui</h1>
            <button on:click={increment}>Click me! Count is {count}</button>
        </>
    )
}

// Render directly onto the DOM
DOMRenderer.render(document.body, App)
// Render to string
const htmlApp = HTMLRenderer.createElement(App)
console.log(HTMLRenderer.serialize(htmlApp)
// yields
// <button>Click me! Count is 0</button>

count.value += 10

nextTick(() => {
    console.log(HTMLRenderer.serialize(htmlApp))
    // yields
    // <button>Click me! Count is 10</button>
    // Count rendered on DOM also changes
})
wooorm commented 1 month ago

This is a different example. You miss what I am saying: it’s not useful.

ClassicOldSong commented 1 month ago

I understand. You don't have use cases for this degree of freedom, but I need them so I created my frameworks in the first place(as all other frameworks are all coupled into web heavily, even assuming your environment is brower and abusing browser specific features) and won't limit their usable scenarios in the future.

I'll still try to find better ways to integrate into MDX but I still suggest to reconsider about classic transformations.

wooorm commented 1 month ago

I don’t understand your use case. I don’t see a use case for live patches to a PDF.

I don’t agree with your point. You at least miss react-native and vadimdemedes/ink.

ClassicOldSong commented 1 month ago

I don't miss react-native as I have made literally any web frameworks usable on mobile platforms: https://github.com/SudoMaker/nativescript-dom-ng

JavaScript has much more potentials and I don't want people limiting all their possibilities to web. The JS runtime I mentioned above can even run on embedded devices that have only 8MB of physical ram, yet rendering GUI applications at 60fps.

I understand that you can't understand what I'm talking about, since seldom people have vision across multiple fields that seem to be distant.

wooorm commented 1 month ago

Now you are insulting me.

If you can’t explain your use case, perhaps that’s something you should improve.

MDX is not coupled to the web.

ClassicOldSong commented 1 month ago

Something isn't explainable if the other participants don't have knowledge in the topic on the same level. I don't mean insulting, and sorry for that if that makes you feel uncomfortable.

I'm bad at explaining things so forgive me if I couldn't make it clear to you. As I have said,

I'll still try to find better ways to integrate into MDX but I still suggest to reconsider about classic transformations.

wooorm commented 1 month ago

OK. Thanks.

This issue tracker is for MDX. The context you provide with your question is not about MDX.