Skip to content

worklets plugin: preserve JSX imports in bundle mode#9212

Open
hannojg wants to merge 5 commits into
software-mansion:mainfrom
hannojg:hannojg/worklets-bundle-mode-jsx-component-capture-option
Open

worklets plugin: preserve JSX imports in bundle mode#9212
hannojg wants to merge 5 commits into
software-mansion:mainfrom
hannojg:hannojg/worklets-bundle-mode-jsx-component-capture-option

Conversation

@hannojg

@hannojg hannojg commented Apr 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Caution

This PR depends on:

This adds support for importing JSX statements/imports in worklets bundle mode!

Test plan

  • Unit tests
  • example app babel plugin tests:
Screenshot 2026-06-06 at 12 56 56

@hannojg hannojg changed the title Add opt-in bundle mode JSX component capture worklets plugin: Add opt-in bundle mode JSX component capture Apr 5, 2026
@hannojg hannojg marked this pull request as ready for review April 5, 2026 16:40
@tjzel tjzel self-assigned this Apr 8, 2026
@hannojg hannojg force-pushed the hannojg/worklets-bundle-mode-jsx-component-capture-option branch from a9f06c3 to 82991ed Compare May 31, 2026 16:09

@tjzel tjzel left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we discussed briefly off-discord - I think there's no need to add a separate option for that. In Bundle Mode we should preserve JSX as long as it comes from modules allowed for imports.

Could you also add a runtime tests suite in /apps/common-app/src/apps/reanimated/examples/RuntimeTests/tests/plugin that checks if worklets with JSX work at runtime?

@hannojg hannojg force-pushed the hannojg/worklets-bundle-mode-jsx-component-capture-option branch from 034c867 to 0470a3f Compare June 6, 2026 10:03
@hannojg hannojg changed the title worklets plugin: Add opt-in bundle mode JSX component capture worklets plugin: preserve JSX imports in bundle mode Jun 6, 2026
@hannojg

hannojg commented Jun 6, 2026

Copy link
Copy Markdown
Contributor Author

Hey @tjzel, i reworked the PR to just preserve JSX in general, without hiding it between an option.

When testing in the example app i ran into this issue though:

I made a "fix" for that here:

This PR depends on the mentioned PR.

@tjzel

tjzel commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

@hannojg I opened a separate PR that seems to be a general fix for multiple passes of generated files #9618, could you see if it can replace your other one?

@hannojg

hannojg commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

I checked this against latest main with #9618 included.

I made a test branch and applied only the JSX capture changes from this PR, without the generated JSX transform fix from #9610. The unit tests and typecheck pass, but bundling the Fabric example in bundle mode still fails with the same JSX dev transform error:

Duplicate __self prop found
packages/react-native-worklets/.worklets/17194764026949.js
return <ImportedComponent __self={this} />;

So #9618 fixes the multiple-pass/autoworkletization case, but it does not seem to cover this specific JSX-in-generated-worklet-file case. So it seems like something like #9610 is still required unfortunately :/

@hannojg hannojg force-pushed the hannojg/worklets-bundle-mode-jsx-component-capture-option branch from 332d63f to 1ecc161 Compare June 10, 2026 09:27
const transformedProg = transformFromAstSync(newProg, undefined, {
filename: state.file.opts.filename,
presets: ['@babel/preset-typescript'],
presets: ['@babel/preset-typescript', [reactPreset, reactPresetOptions]],

@tjzel tjzel Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hannojg

I looked into it and it turns out that the transformation that adds __self is added when entering the whole Program so when generating the worklet file we already have that in.

I think that instead of transforming it with react-preset to _jsx form maybe we should revert the work of that plugin, namely strip the __self and __source props to be closer to a real source file https://babeljs.io/docs/babel-preset-react#development.

A minimal plugin like this:

diff --git a/packages/react-native-worklets/plugin/src/closure.ts b/packages/react-native-worklets/plugin/src/closure.ts
index bac569c82d..34e998b910 100644
--- a/packages/react-native-worklets/plugin/src/closure.ts
+++ b/packages/react-native-worklets/plugin/src/closure.ts
@@ -32,7 +32,7 @@ export function getClosure(
         typePath.skip();
       },
       ReferencedIdentifier(idPath) {
-        if (idPath.isJSXIdentifier()) {
+        if (idPath.isJSXIdentifier() && !state.opts.bundleMode) {
           return;
         }
 
diff --git a/packages/react-native-worklets/plugin/src/generate.ts b/packages/react-native-worklets/plugin/src/generate.ts
index 32906d4244..87a54d9d26 100644
--- a/packages/react-native-worklets/plugin/src/generate.ts
+++ b/packages/react-native-worklets/plugin/src/generate.ts
@@ -4,6 +4,7 @@ import type {
   FunctionExpression,
   ImportDeclaration,
   ImportSpecifier,
+  JSXAttribute,
 } from '@babel/types';
 import {
   cloneNode,
@@ -72,7 +73,7 @@ export function generateWorkletFile(
   const transformedProg = transformFromAstSync(newProg, undefined, {
     filename: state.file.opts.filename,
     presets: ['@babel/preset-typescript'],
-    plugins: [state.autoworkletizationPlugin],
+    plugins: [state.autoworkletizationPlugin, stripJsxDevAttributesPlugin],
     ast: false,
     babelrc: false,
     configFile: false,
@@ -85,3 +86,18 @@ export function generateWorkletFile(
 
   writeFileSync(dedicatedFilePath, transformedProg);
 }
+
+const stripJsxDevAttributesPlugin = {
+  name: 'worklets-strip-jsx-dev-attributes',
+  visitor: {
+    JSXAttribute(path: NodePath<JSXAttribute>) {
+      const name = path.node.name;
+      if (
+        name.type === 'JSXIdentifier' &&
+        (name.name === '__self' || name.name === '__source')
+      ) {
+        path.remove();
+      }
+    },
+  },
+};

solved the issue for me with metro bundling. Could you see if it works for you too?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants