Skip to content

Support publishing with JSX #24

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

Open
lucacasonato opened this issue Feb 28, 2024 · 27 comments
Open

Support publishing with JSX #24

lucacasonato opened this issue Feb 28, 2024 · 27 comments
Assignees

Comments

@lucacasonato
Copy link
Member

lucacasonato commented Feb 28, 2024

To publish with JSX, we need to take the JSX relevant config options from tsconfig.json / deno.json and put them into the source files via pragmas.

For example, when publishing this index.jsx file with this deno.json file:

import { renderToString } from "npm:preact-render-to-string";
console.log(renderToString(<div>jsx</div>));
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "npm:preact"
  }
}

emit this before publish:

/** @jsxRuntime automatic *//** @jsxImportSource npm:preact */
import { renderToString } from "npm:preact-render-to-string";
console.log(renderToString(<div>jsx</div>));
@bartlomieju
Copy link
Contributor

@lucacasonato @dsherret I'm a bit confused by this issue. I thought that we only consider the first encountered directive like that - ie. different directives in different files will override each other. I assume that this situation can't be hit because we're gonna pull the directive value from the config file and it's essentially the first one loaded that will actually have effect. Is this correct?

@lucacasonato
Copy link
Member Author

No - directives are per file.

@lucacasonato
Copy link
Member Author

For now we can just add a warning if your publish contains a .jsx / .tsx file saying that .jsx / .tsx are not supported yet, linking to this issue.

@bartlomieju
Copy link
Contributor

PR adding a warning: denoland/deno#22631

@nestarz
Copy link

nestarz commented Mar 1, 2024

To support React JSX I think this issue should be resolved so JSR published React+JSX modules works also in Deno, right ?

denoland/deno#18203

@lucacasonato lucacasonato added this to JSR Mar 4, 2024
@github-project-automation github-project-automation bot moved this to Needs Triage in JSR Mar 4, 2024
@lucacasonato lucacasonato moved this from Needs Triage to Ready in JSR Mar 4, 2024
@neves
Copy link

neves commented May 26, 2024

@nestarz the above issue was resolved, what's the next step to support jsx?

@neves
Copy link

neves commented Jun 6, 2024

@lucacasonato any take on this one?

@KyleJune
Copy link

Any updates on this? Also is there a workaround involving manually adding the pragmas that would enable publishing jsx to jsr?

@KyleJune
Copy link

KyleJune commented Jul 24, 2024

Here is one other issue present that may cause issues for packages with react dependencies.

kyle@DESKTOP-6071BGK:~/Projects/deno/react_app$ deno publish --dry-run
Check file:///home/kyle/Projects/deno/react_app/mod.tsx
Check file:///home/kyle/Projects/deno/react_app/build.ts
Check file:///home/kyle/Projects/deno/react_app/dev.ts
Check file:///home/kyle/Projects/deno/react_app/client.tsx
Check file:///home/kyle/Projects/deno/react_app/server.tsx
error: Uncaught Error: [ERR_PACKAGE_PATH_NOT_EXPORTED] Package subpath './client' is not defined for types by "exports" in '/home/kyle/.cache/deno/npm/registry.npmjs.org/react-dom/18.3.1/package.json' imported from 'file:///home/kyle/Projects/deno/react_app/client.tsx'
    at Object.resolveModuleNames (ext:deno_tsc/99_main_compiler.js:766:28)
    at actualResolveModuleNamesWorker (ext:deno_tsc/00_typescript.js:124316:142)
    at resolveModuleNamesWorker (ext:deno_tsc/00_typescript.js:124748:20)
    at resolveModuleNamesReusingOldState (ext:deno_tsc/00_typescript.js:124834:14)
    at processImportedModules (ext:deno_tsc/00_typescript.js:126351:118)
    at findSourceFileWorker (ext:deno_tsc/00_typescript.js:126129:7)
    at findSourceFile (ext:deno_tsc/00_typescript.js:125980:20)
    at processImportedModules (ext:deno_tsc/00_typescript.js:126377:11)
    at findSourceFileWorker (ext:deno_tsc/00_typescript.js:126129:7)
    at findSourceFile (ext:deno_tsc/00_typescript.js:125980:20)

Here are the compiler options that I use along with my react imports.

{
  "compilerOptions": {
    "lib": ["esnext", "dom", "dom.iterable", "dom.asynciterable", "deno.ns"],
    "jsx": "react-jsx",
    "jsxImportSource": "react",
    "jsxImportSourceTypes": "@types/react"
  },
  "imports": {
    "react": "npm:react@18",
    "@types/react": "npm:@types/react@18",
    "react-dom": "npm:react-dom@18",
  }
}

Hope this information is helpful. I think it's important to support JSX and react on JSR. It will help with adoption of Deno and JSR for building front end applications. My code works when I run it with deno, it's just not working with the deno publish command.

Edit: I checked my code and found only one line referencing react-dom/client. It's only actually used on the client side in my esbuild bundle.

import { hydrateRoot } from "react-dom/client";

I tried moving it inside the function that only ever gets called in the browser and making it a dynamic import but I get the same error.

const { hydrateRoot } = await import("react-dom/client");

I also checked the package.json that the error references and it is defined there.

  "files": [
    "LICENSE",
    "README.md",
    "index.js",
    "client.js",
    "profiling.js",
    "server.js",
    "server.browser.js",
    "server.node.js",
    "test-utils.js",
    "cjs/",
    "umd/"
  ],
  "exports": {
    ".": "./index.js",
    "./client": "./client.js",
    "./server": {
      "deno": "./server.browser.js",
      "worker": "./server.browser.js",
      "browser": "./server.browser.js",
      "default": "./server.node.js"
    },
    "./server.browser": "./server.browser.js",
    "./server.node": "./server.node.js",
    "./profiling": "./profiling.js",
    "./test-utils": "./test-utils.js",
    "./package.json": "./package.json"
  },

Edit 2: I found I can run the publish command by adding the --no-check flag.

@KyleJune
Copy link

I tried doing a manual fix, it looks like adding these pragmas to my files doesn't get rid of the warning regarding jsx/tsx files not being supported. I haven't checked if it allows you to publish if you get the warning. I'll try that out once I finish the package I'm working on.

/** @jsxRuntime automatic */
/** @jsxImportSource npm:react@18 */
/** @jsxImportSourceTypes npm:@types/react@18 */
warning[unsupported-jsx-tsx]: JSX and TSX files are currently not supported
 --> /home/kyle/Projects/deno/react_app/client.tsx

  info: follow https://github.com/jsr-io/jsr/issues/24 for updates

@neves
Copy link

neves commented Jul 26, 2024

I tried doing a manual fix, it looks like adding these pragmas to my files doesn't get rid of the warning regarding jsx/tsx files not being supported. I haven't checked if it allows you to publish if you get the warning. I'll try that out once I finish the package I'm working on.

/** @jsxRuntime automatic */
/** @jsxImportSource npm:react@18 */
/** @jsxImportSourceTypes npm:@types/react@18 */
warning[unsupported-jsx-tsx]: JSX and TSX files are currently not supported
 --> /home/kyle/Projects/deno/react_app/client.tsx

  info: follow https://github.com/jsr-io/jsr/issues/24 for updates

@KyleJune, today this warning is hard coded https://github.com/denoland/deno/pull/22631/files

@KyleJune
Copy link

KyleJune commented Jul 28, 2024

I've verified that adding the pragmas and ignoring the warnings about JSX and TSX files not being supported allowed me to publish to JSR. I haven't tried publishing without the pragmas, I just know it works with them. I'm assuming it doesn't with out them by the existence of this issue.

I'm still working on it, but you can see the published package containing JSX here.
https://jsr.io/@udibo/react-app

It's not quite ready for others to use but it works. Here is a repo you could fork to see that it's able to import JSX modules from JSR.

https://github.com/udibo/react-app-example

One thing worth noting is that it seems building is slower when I use the package from JSR. In that example repo, I consistently get build times around ~1200ms.

INFO Detected change: /home/kyle/Projects/deno/react_app_example/routes/index.tsx
INFO Building app
INFO Build completed in 1171 ms
INFO Restarting app
INFO Listening on: http://localhost:9000
INFO Server restarted

The same example is present in the repo for my published package. There it consistently takes around 650ms, almost half the time it does when I'm using it through jsr.
https://github.com/udibo/react-app/tree/main/example

INFO Detected change: /home/kyle/Projects/deno/react_app/example/routes/index.tsx
INFO Building app
INFO Build completed in 643 ms
INFO Restarting app
INFO Listening on: http://localhost:9000
INFO Server restarted

The only real difference between the 2 is that one is importing the build script from JSR and the other is using it locally since it is present in the parent directory.

I'm not sure if it's that esbuild is slower or if it has something to do with the dynamic imports the script is doing. I filed a separate issue regarding a workaround I had to do to be able to dynamically import user files in my build script.

#670

Edit: I improved my logging, adding additional performance measurements. It appears the slowdown is specifically related to esbuild. Same build script running, only difference being that one is importing it from JSR and the other is importing it from a local file. Both depend on npm:[email protected].

@tlgimenes
Copy link

Why do I need to publish .js files only? Can't I publish a .jsx so that, if I'm using fresh, fresh will use it's own jsx config. If I'm using next.js, next.js will use it's own .jsx config. This way I can support both Fresh(preact) and Next.js(react)

@ghandic
Copy link

ghandic commented Aug 27, 2024

What are the dependencies for this to be resolved? There isnt much mentioned on this in the docs and its quite a sizable gap to move from npm

@sgwilym
Copy link

sgwilym commented Sep 15, 2024

I've been sitting on top of a suite of macro libraries which I can't release because of the lack of this publishing with JSX, and for which we still rely on deno.land/x. Please bring publishing with JSX to JSR!

@grenierdev
Copy link

I was able to publish my .tsx to JSR without many issues (see for yourself @baseless/react).

My only issue is that when I use npx jsr add @baseless/react in a Nodejs project, the resulting package only contains the .tsx, no .js nor .js.map.

I've been using this script to locally build my modules for local development with npm link. I guess I went the extra mile and converted my .tsx to .js,.js.map,.d.ts.

I wouldn't mind looking at adding JSX support to the build process if someone could point me in the right direction. (I know my way around Rust too).

@imcotton
Copy link

imcotton commented Dec 5, 2024

I have used this esbuild plugin to do the extra refinement:

const esbuild_plugin = {

    name: 'hotfix',

    setup (build) {

        build.onResolve({ filter: /^jsr:/ }, ({ path, kind, resolveDir }) => {

            const fixed = path
                .replace(/^jsr:\/?@/, 'AT-JSR')
                .replace('/', '__')
                .replace(/@[^/]+/, '')
                .replace('AT-JSR', '@jsr/')
            ;

            return build.resolve(fixed, { kind, resolveDir });

        });

        build.onResolve({ filter: /\.ts$/ }, async ({ path, kind, resolveDir }) => {

            if (resolveDir.includes('/node_modules/@')) {

                const fixed = path.replace(/ts$/, 'js');

                return build.resolve(fixed, { kind, resolveDir });

            }

        });

    },

};

@hansSchall
Copy link

Can you please provide a CLI flag to disable the warning until this has been fixed.

It seems to work just fine as long as you dont rely on transpilation. I am using my own vite plugin for handling JSR imports (https://github.com/deno-plc/vite-plugin-deno) in the frontend and like Deno it is able to directly use the .ts(x) files

den9yon9 added a commit to den9yon9/trok that referenced this issue Jan 6, 2025
@mbhrznr
Copy link
Contributor

mbhrznr commented Jan 9, 2025

@lucacasonato @dsherret i'd love to give it a stab, if you don't mind.
i already started to do some digging and checked the relevant apis.
when it comes to emitting the pragmas, is there any priority when it comes to deriving the info from the config files?
i'd also assume that in the case that a directive already exists for a given file we won't override it?

@crowlKats crowlKats marked this as a duplicate of #840 Jan 16, 2025
@crowlKats crowlKats removed the bug label Jan 17, 2025
@crowlKats crowlKats moved this from Ready to In Progress in JSR Jan 17, 2025
NfNitLoop added a commit to diskuto/diskuto-web that referenced this issue Jan 27, 2025
@lucacasonato
Copy link
Member Author

JSX publishing is now supported!

@drernie
Copy link

drernie commented Feb 24, 2025

So will we close this ticket? Do we need a new version of Deno?

@neves
Copy link

neves commented Mar 6, 2025

Has any one been able to successfully publish a jsx and than use it in another project?
I was able to publish it https://jsr.io/@neves/utils/doc/~/Teste
but when running I'm getting: Cannot find module jsr:@bossley9/sjsx@^0.12.5/jsx-dev-runtime

@hansSchall
Copy link

I have successfully published TSX, it works fine with the Deno LSP (we are using Deno even for the frontend), SSR is not tested yet but probably works.

We use Vite with a custom plugin (https://github.com/deno-plc/vite-plugin-deno) for frontend bundling/Devserver.

With this plugin we were already able to use TSX in JSR before this was merged, because it treats TSX files like local files with virtual paths. After the change we had to adjust it to ignore the jsx/compiler directives, because of similar errors. To me it does not make sense to use a different JSX lib than the whole project.

manudeli pushed a commit to toss/suspensive that referenced this issue Mar 25, 2025
# Overview

<!--
    A clear and concise description of what this pr is about.
 -->

Attempting to resolve JSR publishing issue. Testing with
@suspensive/react package first.

Related issues:
- jsr-io/jsr#526
- jsr-io/jsr#632
- jsr-io/jsr#24
- jsr-io/jsr/discussions/399 

## PR Checklist

- [x] I did below actions if need

1. I read the [Contributing
Guide](https://github.com/toss/suspensive/blob/main/CONTRIBUTING.md)
2. I added documents and tests.
@alexgleason
Copy link

I managed to publish JSX, eg: https://jsr.io/@nostrify/react/0.0.1-alpha.6/login/NostrLoginProvider.tsx

But when using the package in a vite project (also with Deno), I get an error: Uncaught TypeError: jsx is not a function

I've been trying a bunch of different things to no avail.

@alexgleason
Copy link

alexgleason commented Apr 11, 2025

@dsherret Check out the pragmas at the start of this file on JSR:

/** @jsxRuntime automatic *//** @jsxImportSource npm:react@^19.1.0 *//** @jsxImportSourceTypes npm:@types/react@^19.1.0 *//** @jsxFactory React.createElement *//** @jsxFragmentFactory React.Fragment */import { type FC, type ReactNode } from 'npm:react@^19.1.0';

import { NostrLoginContext, NostrLoginContextType } from './NostrLoginContext.ts';
import { useNostrLoginReducer } from './useNostrLoginReducer.ts';

// ...rest of file

The lack of newlines could be a problem?

The file: https://jsr.io/@nostrify/react/0.0.1-alpha.6/login/NostrLoginProvider.tsx

EDIT: I tried manually adding the pragma statements and republishing, and it still didn't fix it 🤦

EDIT2: The compiled file looks like this:

import __vite__cjsImport0_npm_react__19_1_0_jsxRuntime from "/node_modules/.vite/deps/react.js?v=e181facd"; const jsx = __vite__cjsImport0_npm_react__19_1_0_jsxRuntime["jsx"];
import { NostrLoginContext } from "/@id/__x00__deno::TypeScript::https:/jsr.io/@nostrify/react/0.0.1-alpha.7/login/NostrLoginContext.ts::/home/alex/.cache/deno/remote/https/jsr.io/3841257570f18d60a8e8eb750f9257824b41e95ca27c5559366f9bc0fb342f6f";
import { useNostrLoginReducer } from "/@id/__x00__deno::TypeScript::https:/jsr.io/@nostrify/react/0.0.1-alpha.7/login/useNostrLoginReducer.ts::/home/alex/.cache/deno/remote/https/jsr.io/07191f0f2fdd432e5ddff116ba6d087cc69dbc6566ea9232e4a0d266cca2db56";
const NostrLoginProvider = ({ children, storageKey }) => {
  const [logins, dispatch] = useNostrLoginReducer(storageKey);
  const value = {
    logins,
    addLogin: (login) => dispatch({ type: "login.add", login }),
    removeLogin: (id) => dispatch({ type: "login.remove", id }),
    setLogin: (id) => dispatch({ type: "login.set", id }),
    clearLogins: () => dispatch({ type: "login.clear" })
  };
  return /* @__PURE__ */ jsx(NostrLoginContext.Provider, { value, children });
};
export {
  NostrLoginProvider
};

Same code as a screenshot:

Image

EDIT: This problem was being caused by deno-vite-plugin. See the PRs and comment below.

@alexgleason
Copy link

I ended up working around my issues by just not using TSX. I renamed the file to .ts, added import { jsx } from 'react/jsx-runtime'; at the top, then used return jsx(MyComponent, { ...props, children }); inside my component instead of returning JSX. Now it works fine when published to JSR and imported by other projects. I could do this because I was only trying to distribute a single TSX file that was very simple.

@danopia
Copy link

danopia commented May 16, 2025

I have some code that renders jsx only on the backend for serving plain HTML. After some trial+error, I ended up getting TSX working in JSR packages by replacing the previous pragmas + import with the new style of pragmas:

/** @jsxRuntime automatic *//** @jsxImportSource jsr:@hono/[email protected]/jsx */

// then simply use jsx:
export const Comp = () => (
  <div>hello</div>
);

It feels a bit strange to me to have an import descriptor in a comment instead of an import statement like I had before. But it seems to work this way

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: In Progress
Development

No branches or pull requests