jsr icon indicating copy to clipboard operation
jsr copied to clipboard

Support publishing with JSX

Open lucacasonato opened this issue 1 year ago • 27 comments
trafficstars

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>));

lucacasonato avatar Feb 28 '24 15:02 lucacasonato

@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?

bartlomieju avatar Feb 28 '24 16:02 bartlomieju

No - directives are per file.

lucacasonato avatar Feb 28 '24 16:02 lucacasonato

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.

lucacasonato avatar Feb 28 '24 16:02 lucacasonato

PR adding a warning: https://github.com/denoland/deno/pull/22631

bartlomieju avatar Feb 29 '24 09:02 bartlomieju

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

https://github.com/denoland/deno/issues/18203

nestarz avatar Mar 01 '24 12:03 nestarz

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

neves avatar May 26 '24 14:05 neves

@lucacasonato any take on this one?

neves avatar Jun 06 '24 06:06 neves

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

KyleJune avatar Jul 22 '24 01:07 KyleJune

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 avatar Jul 24 '24 03:07 KyleJune

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 avatar Jul 25 '24 03:07 KyleJune

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

neves avatar Jul 26 '24 01:07 neves

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.

https://github.com/jsr-io/jsr/issues/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].

KyleJune avatar Jul 28 '24 04:07 KyleJune

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)

tlgimenes avatar Aug 06 '24 14:08 tlgimenes

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

ghandic avatar Aug 27 '24 03:08 ghandic

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!

sgwilym avatar Sep 15 '24 10:09 sgwilym

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).

grenierdev avatar Sep 15 '24 23:09 grenierdev

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 });

            }

        });

    },

};

imcotton avatar Dec 05 '24 12:12 imcotton

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

hansSchall avatar Dec 31 '24 12:12 hansSchall

@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?

mbhrznr avatar Jan 09 '25 20:01 mbhrznr

JSX publishing is now supported!

lucacasonato avatar Feb 23 '25 21:02 lucacasonato

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

drernie avatar Feb 24 '25 06:02 drernie

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

neves avatar Mar 06 '25 22:03 neves

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.

hansSchall avatar Mar 07 '25 07:03 hansSchall

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 avatar Apr 11 '25 01:04 alexgleason

@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 avatar Apr 11 '25 02:04 alexgleason

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.

alexgleason avatar Apr 17 '25 19:04 alexgleason

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

danopia avatar May 16 '25 23:05 danopia