react-strict-dom icon indicating copy to clipboard operation
react-strict-dom copied to clipboard

Error using vite with RSD: stylex.create'should never be called at runtime.

Open carloslibardo opened this issue 10 months ago • 12 comments

Describe the issue

I created a react-ts app using vite cli to react-ts project, then add RSD library and follow setup steps.

When trying to render anything using RSD running vite command to run Vite Application this errors happens:

Uncaught Error: 'stylex.create' should never be called at runtime. It should be compiled away by '@stylexjs/babel-plugin'

Below this thread have more about discussion of this problem, possible Vite is not processing correctly RSD at runtime.

Expected behavior

Works a hello world project without any errors.

Steps to reproduce

1 - create react-ts app using vite: npm create vite@latest my-react-app -- --template react-ts 2 - add RSD into project 3 - make setup following the docs. 4 - try to render simple any DOM using RSD 5 - run yarn dev to run project

Test case

https://codesandbox.io/p/github/carloslibardo/website/main

Additional comments

Already tried to add "node_modules/react-strict-dom/dist/dom/runtime.js" into postcss.config.js and does not work, if i comment runtime.js stylex code it works, but still cannot apply my styles using stylex at my components.

carloslibardo avatar Jan 29 '25 21:01 carloslibardo

https://codesandbox.io/p/github/carloslibardo/website/main

"Unable to access this workspace"

necolas avatar Feb 12 '25 21:02 necolas

I recently got a Vite project running with Vite 6 and all latest versions 🙂

  1. Run yarn create vite, install react-strict-dom and postcss-react-strict-dom

  2. Change vite.config.ts

import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';
import babel from 'vite-plugin-babel';

// https://vite.dev/config/
export default defineConfig({
  plugins: [
    react({
      babel: {
        presets: ['react-strict-dom/babel-preset']
      }
    }),
    babel({
      include: /node_modules\/react-strict-dom(-svg)?/
    })
  ]
});
  1. Create babel.config.cjs (only .cjs works with Node.js 20)
module.exports = {
  presets: [
    ['@babel/preset-react', { runtime: 'automatic' }],
    'react-strict-dom/babel-preset'
  ]
};
  1. Create postcss.config.js
export default {
  plugins: {
    'postcss-react-strict-dom': {
      include: ['src/**/*.{js,jsx,ts,tsx}']
    }
  }
};
  1. Add @stylex; at the top of your index.css file

efoken avatar Feb 14 '25 19:02 efoken

Is the cjs limitation because we aren't providing mjs exports from the packages?

necolas avatar Feb 14 '25 21:02 necolas

@necolas This is possibly the problem because Vite (like all bundlers) has many idiosyncrasies. However, Node has always been able to import CJS from ESM so CJS is usually considered the safer thing to publish to NPM.

We did run into similar issues with Vite before which is why we ended up publishing .mjs exports for some of the packages in StyleX.

nmn avatar Feb 15 '25 03:02 nmn

Correction: postcss.config.js is also working without any problem. When working with Node < 22, you have to use babel.config.cjs to get vite-plugin-babel working correctly, but I found out that .js with ESM is also possible with NODE_OPTIONS=--experimental-require-module, or when using Node.js 22

efoken avatar Feb 15 '25 08:02 efoken

I'm having the same issue as @carloslibardo I created #273 thinking it was a different issue but I think it is the same issue. I tried efoken configs but with no luck. I can also reproduce the issue using metro bundler. I am using pnpm and node 22. Here is a repository where you can see the issue with tsup & metro: https://github.com/fredericrous/rds-metro Here is a repository where you can see the issue with vite: https://github.com/fredericrous/rds-vite

fredericrous avatar Feb 21 '25 18:02 fredericrous

@necolas could you explain the thumbs down on my message? Did you try one of these repository? Have you validated on your side efoken configuration? if this repo provided an example on how to create a component lib, export it and use it, it would be appreciated

fredericrous avatar Feb 21 '25 19:02 fredericrous

I was on my phone and just mis-tapped the reaction icon

necolas avatar Feb 22 '25 23:02 necolas

@fredericrous its not the same issue. The issue is that you try to run your web bundle of the ui package on native. In both of your repos, with Vite and also with Metro. Using PostCSS and a CSS file is for web.

Also in your storybook package I don't see the babel-preset and RSD setup, so you probably have not read/understood the documentation correctly or maybe never built a React Native ui library before.

When you want to run your ui library on native there is no need to build it before. Especially not with Web bundlers

efoken avatar Feb 23 '25 07:02 efoken

thank you for the explanation @efoken

Indeed the storybook package has no babel-preset configured because I was expecting the library to do the transformation. You're right about my inexperience with building a UI library for native. There is very little documentation on this topic, but thanks to your valuable insights, I should be able to move forward.

I believe the value of react-strict-dom is not only to build like web on native but to enable sharing the same components across projects (both web and native). The documentation explains well how to use RDS directly on native or on web (and I successfully did use RDS on native). However, there seems to be a lack of documentation regarding how to create a reusable UI library. An example might be more effective than just documentation in clarifying this process.

That said, this discussion is slightly off-topic from the issue at hand. I hope the Vite-related problem described here finds a resolution

fredericrous avatar Feb 23 '25 09:02 fredericrous

@necolas @efoken

bit of a long shot because I am still not able to replicate in a simpler setup... but maybe you have some ideas or encountered similar issues.

With a config similar to https://github.com/facebook/react-strict-dom/issues/265#issuecomment-2660129604 I got everything to work with a few interesting changes.

// babel.config.js

/* global process */

function getPlatform(caller) {
  return caller && caller.platform;
}

function getIsDev(caller) {
  return !!caller?.isDev;
}

export default function (api) {
  const platform = api.caller(getPlatform) ?? "native";

    console.log(`Babel config for platform: ${platform}`);

  const dev = api.caller(getIsDev);

  const isTest = process.env.NODE_ENV === "test";

  api.cache.using(() => process.env.NODE_ENV === "development");

  const plugins = ["module:react-native-reanimated/plugin"];

  if (!isTest) {
    plugins.push([
      "@stylexjs/babel-plugin",
      {
        debug: dev,
        dev: dev,
        importSources: [{ from: "react-strict-dom", as: "css" }],
        runtimeInjection: false,
        styleResolution: "property-specificity",
        unstable_moduleResolution: {
          rootDir: process.cwd(),
          type: "commonJS",
        },
      },
    ]);
  }

  return {
    plugins,
    presets: [
      ["module:@react-native/babel-preset"],
      [
        "module:react-strict-dom/babel-preset",
        {
          platform,
          debug: dev,
          dev,
        },
      ],
    ],
  };
}

Here I had to add the seemingly redundant @stylexjs/babel-plugin that in theory should be added with the exact same settings by the preset. No clue why...

// vite.config.js

import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
import babel from "vite-plugin-babel";

export default defineConfig(({ mode }) => ({
  plugins: [
    react({
      babel: {
        presets: [
          [
            "react-strict-dom/babel-preset",
            {
              debug: mode === "development",
              dev: mode === "development",
              platform: "web",
            },
          ],
        ],
      },
    }),
    babel({
      include: /node_modules\/react-strict-dom/,
    }),
  ],
}));

// postcss.config.js

import path from "node:path";
import { globSync } from "node:fs";

function getIncludePath(packageName) {
  const url = new URL(import.meta.resolve(packageName));
  return [
    path.dirname(url.pathname) + "/**/*.{js,jsx,mjs,ts,tsx}",
    "!" + path.dirname(url.pathname) + "/node_modules/**/*.{js,jsx,mjs,ts,tsx}",
  ];
}

function getSources() {
  const paths = globSync("../**/*.{ts,tsx}", {
    cwd: import.meta.dirname,
    withFileTypes: true,
    exclude: (dirent) => dirent.parentPath.includes("node_modules"),
  });

  return paths.map((file) => path.resolve(file.parentPath, file.name));
}

export default {
  plugins: {
      "react-strict-dom/postcss-plugin": {
      include: [
        ...getSources(),
        ...getIncludePath("@my-org/tokens.stylex"),
        ...getIncludePath("react-strict-dom"),
      ],
      useCSSLayers: true,
    },
  },
};

Here the complex getSources is needed to overcame the issue with pnpm using symlinks

Now, when I updated to newer versions of react-strict-dom that internally updated stylex to 0.13.1 first and then 0.14.1, this fragile setup stopped working and started showing Only static values are allowed inside of a createTheme() call error.

I did some digging and what I discovered is that code like

const baseTokens = {
  colorBackgroundDefault: { default: "#ffffff", [dark]: "#000000" },
  colorBackgroundPrimary: { default: "#000000", [dark]: "#ffffff" },
  colorBackgroundSecondary: { default: "#66676e", [dark]: "#b6b8bc" },

gets transformed at some unknown point in the pipeline to calls to _defineProperty (babel very old polyfill for [dark]: ... syntax) and the stylex visitor is not able to recognize that form as a static value.

I have no clue why babel is doing that, and I don't see any _defineProperty in the generated files on the vite dev server, so I think it must be happening in an intermediate step.

So my questions are:

  • do you have any idea about this _defineProperty step looking at the config? Or maybe encountered same issue in the past?
  • do you have any clue why that redundant plugin is needed?

I know is a long shot, but hopefully my use case can also help identifying some additional sources of possible issues and gradually get to a point where we can have a reliable working setup to document for both storybook (native + web) as well as usage with pnpm

axyz avatar Jul 08 '25 08:07 axyz

I'm going to answer myself, at least on the _defineProperty issue.

It seems that the problem is sharing the react-native babel-preset with the web vite config as well.

One alternative could be to pass babelrc: false and manually recreate a typescript config for vite, but if we want to reuse the same profile used by react-native, then we need to explicitly pass

presets: [
      ["module:@react-native/babel-preset",
        {
          unstable_transformProfile: "hermes-stable",
        }
      ],

without the unstable_transformProfile, the preset by default assumes the hermes architecture is not used and applies the very old @babel/plugin-transform-computed-properties transform that generates those _defineProperty that makes the stylex compiler fail to recognize the static value

Hope this can be useful

axyz avatar Jul 08 '25 09:07 axyz

I guess my config could help to start a doc for vite https://github.com/facebook/react-strict-dom/discussions/430#discussioncomment-15146095

MoOx avatar Dec 03 '25 09:12 MoOx

I'm joining @MoOx’s comment. I think that with our two configuration examples #430 (comment) , we now have a solid basis for setting up with Vite (and TanStack Start).

Dovakeidy avatar Dec 03 '25 14:12 Dovakeidy

Nice work. Would be great to have a PR to add a Vite setup in the repo's app examples

necolas avatar Dec 03 '25 17:12 necolas