one icon indicating copy to clipboard operation
one copied to clipboard

Add `nativewind@4` support

Open ceopaludetto opened this issue 1 year ago • 2 comments
trafficstars

It would be amazing if we could use nativewind with one, but a lot of things should be made to do so.

  1. In order to nativewind@4 work we need a vite plugin which transforms css to react-native-css-interop styles, essentially this:
// vite.config.ts
import type { Plugin } from "vite"
import { cssToReactNativeRuntime } from 'react-native-css-interop/css-to-rn'

function nativewind(): Plugin {
  return {
    name: "nativewind",
    transform: (code, id) => {
      if (id.endsWith(".css")) {
        const res = cssToReactNativeRuntime(code)
        return { code: `import { StyleSheet } from "nativewind"\nStyleSheet.registerCompiled(${JSON.stringify(res)})`, map: null }
      }

      return { code }
    }
  }
}
  1. We also need to change jsxImportSource to nativewind, this seems kinda impossible without patching right now (I've tried to do a enforce: "post" plugin that replaces react/jsx-runtime with nativewind/jsx-runtime but it doesn't work)

  2. And we also need a babel plugin nativewind/babel which essentially replaces createElement from react imports with createInteropElement from react-native-css-interop (this plugin is only necessary when the user explicit calls createElement instead of using JSX)

So instead of adding all these things, we can allow users to change the jsxImportSource and also allow then to add some sort of babel plugin that run on the entire codebase. The vite plugin could live on another repository.

What you think?

ceopaludetto avatar Oct 12 '24 20:10 ceopaludetto

It’s definitely doable, Vite is if anything super configurable.

I am surprised at how much setup they have to make that work, wow. It almost seems easier to make Tamagui work with tailwind classNames in a funny way, especially since it has no setup cost at all. I’ve been considering doing this, a bit more as of late. Maybe it’s worth me trying to put together a prototype. The nice thing is it may actually be faster on web and native, and easier to set up. But no guarantee.

For getting nativewind to work, you’ll want to configure things using Vite 6 environments api for theios and android environment.

natew avatar Oct 13 '24 00:10 natew

Separately, we probably could make adding babel and other things easier independently, and allowing this configuration for jsxImportSource, but it'd need to be done well so that it works well with Vite environment APIs. If you make progress on that feel free to push a PR and I'll help along.

natew avatar Oct 13 '24 01:10 natew

I'm looking into this too, @ceopaludetto lmk if I can help :)

pogiii avatar Oct 17 '24 12:10 pogiii

Any update? 👀

CarlosUtrilla avatar Nov 20 '24 18:11 CarlosUtrilla

tamagui but expressed as tw would be fire

joernroeder avatar Dec 28 '24 22:12 joernroeder

i'm redoing babel setup here:

  • https://github.com/onejs/one/pull/374

once that lands i will revisit this as it should fix the babel side

natew avatar Jan 07 '25 22:01 natew

Got it to here:

CleanShot 2025-01-09 at 23 25 15@2x

It seems to be transforming it right:

CleanShot 2025-01-09 at 23 25 41@2x

And you can see its getting to CSSInterop.View, but maybe its grabbing the web path? I tried to delete the web files just to see but still got this. Not sure.

natew avatar Jan 10 '25 09:01 natew

react-native-css-interop was added to optmizeDeps? Maybe you need to set process.env.NATIVEWIND_OS to web or native, nativewind need this to correctly pick the preset to tailwindcss. I've created the following vite plugin to fix that:

{
  name: "nativewind",
  enforce: "pre",
  buildStart() {
    process.env.NATIVEWIND_OS = ["android", "ios"].includes(this.environment.name) ? "native" : "web"
  }
}

See: https://github.com/nativewind/nativewind/blob/main/packages/nativewind/src/tailwind/index.ts

ceopaludetto avatar Jan 10 '25 14:01 ceopaludetto

When you say that is incorrectly picking the web version, do you mean the jsxImportSource? Adding the className directly to the View works? Like this:

import { View, Text } from "react-native"

export default function Index() {
  return (
    <View className="bg-red-500 p-4">
      <Text>Hi</Text>
    </View>
  )
}

If you're getting typescript errors, try this in a nativewind.d.ts file:

/// <reference types="nativewind/types" />

ceopaludetto avatar Jan 10 '25 14:01 ceopaludetto

no className on View and <div /> both fail the same way:

CleanShot 2025-01-12 at 10 35 48@2x

I tried looking again this morning, it seems to be importing all the right nativewind/css-interop native files. Lots of console logs look right.

But what happens in the end is the CssInterop.View that it renders ends up with it's children prop being the original <div />. So it does render the right nativewind view, and I can see the source looks fine, I have to think that somehow nativewind is being run but not transforming the props it gets into the right final form, which feels like it's some sort of case where One is either not grabbing a .native file somewhere, or there's some like other detection inside nativewind that is going down the wrong path and thinks its on web for some reason.

natew avatar Jan 12 '25 20:01 natew

Here's the PR:

  • https://github.com/onejs/one/pull/408

You can run yarn dev:example tailwind and if you add // debug to the top of any file see the transforms, they look right.

natew avatar Jan 12 '25 20:01 natew

Here's the PR:

* [feat(one): support nativewind with no configuration #408](https://github.com/onejs/one/pull/408)

You can run yarn dev:example tailwind and if you add // debug to the top of any file see the transforms, they look right.

I'm trying right now

ceopaludetto avatar Jan 14 '25 14:01 ceopaludetto

After trying for a while, some notes:

  1. jsxImportSource should be nativewind even on the web, since this should enable className support for rn components such as <View /> and <Text />. I've made this work by changing a condition in packages/compiler/src/transformSwc.ts:40 to:
const enableNativeCSS = configuration.enableNativeCSS
  1. nativewind/jsx-runtime fails on SSR due to being a cjs dependency, maybe it needs vite interop. I've got the following error and didn't manage to solve (even using deps configuration), so instead I've changed to spa mode just to continue testing
Stack
ReferenceError: module is not defined
    at eval (/Users/carlos/Documents/Projetos/Javascript/one/node_modules/nativewind/jsx-dev-runtime/index.js:1:8)
    at ESModulesEvaluator.runInlinedModule (file:///Users/carlos/Documents/Projetos/Javascript/one/node_modules/vite/dist/node/module-runner.js:1057:6)
    at ModuleRunner.directRequest (file:///Users/carlos/Documents/Projetos/Javascript/one/node_modules/vite/dist/node/module-runner.js:1271:82)
    at ModuleRunner.cachedRequest (file:///Users/carlos/Documents/Projetos/Javascript/one/node_modules/vite/dist/node/module-runner.js:1166:28)
    at request (file:///Users/carlos/Documents/Projetos/Javascript/one/node_modules/vite/dist/node/module-runner.js:1215:79)
    at async eval (/Users/carlos/Documents/Projetos/Javascript/one/examples/one-tailwind/app/index.tsx:3:44)
    at async ESModulesEvaluator.runInlinedModule (file:///Users/carlos/Documents/Projetos/Javascript/one/node_modules/vite/dist/node/module-runner.js:1049:5)
    at async ModuleRunner.directRequest (file:///Users/carlos/Documents/Projetos/Javascript/one/node_modules/vite/dist/node/module-runner.js:1271:61)
    at async ModuleRunner.cachedRequest (file:///Users/carlos/Documents/Projetos/Javascript/one/node_modules/vite/dist/node/module-runner.js:1167:76)
    at async ModuleRunner.import (file:///Users/carlos/Documents/Projetos/Javascript/one/node_modules/vite/dist/node/module-runner.js:1104:12)
    at async Object.handlePage (file:///Users/carlos/Documents/Projetos/Javascript/one/packages/one/dist/esm/vite/plugins/fileSystemRouterPlugin.mjs:52:52)
    at async file:///Users/carlos/Documents/Projetos/Javascript/one/packages/one/dist/esm/createHandleRequest.mjs:82:97
    at async runMiddlewares (file:///Users/carlos/Documents/Projetos/Javascript/one/packages/one/dist/esm/createHandleRequest.mjs:9:36)
    at async file:///Users/carlos/Documents/Projetos/Javascript/one/packages/one/dist/esm/createHandleRequest.mjs:82:38
    at async file:///Users/carlos/Documents/Projetos/Javascript/one/packages/one/dist/esm/vite/resolveResponse.mjs:10:26
Error rendering / on server
 module is not defined
  1. On native this plugin should be the last plugin run on css files (the inlineRem is aligned with web expectations, I don't know if this should be an userland option, also for some reason, when I add enforce: post, the code for the id base.css is empty):
{
  name: "nativewind:css",
  transform(code, id) {
    if(["android", "ios"].includes(this.environment.name) && id.endsWith(".css")) {
      const data = JSON.stringify(cssToReactNativeRuntime(code, { inlineRem: 16 }))
      return { code:`require("nativewind").StyleSheet.registerCompiled(${data})`, map: null }
    }
  }
}
  1. divs are not supposed to work in react-native when using react-native-css-interop

After doing the step 1 modification. I was able to render correctly on web (I also set the native.css to true, but as far as I read this should be automatically set when nativewind exists in package.json right?):

Screenshot 2025-01-14 at 12 30 03
  1. On tailwindcss configuration, nativewind/preset should be added, this adds a @cssInterop nativewind; at-rule which is transformed to a flag nativewind: true by the cssToReactNativeRuntime function later on
/** @type {import('tailwindcss').Config} */
export default {
  content: [
    "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",
    "./app/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  presets: [require("nativewind/preset")],
  plugins: [require("tailwindcss-animate"), require("tailwindcss-motion")],
};
  1. In order to nativewind/preset be correctly picked I've added the following plugin to vite:
{
  name: "nativewind:pre",
  enforce: 'pre',
  buildStart() {
    process.env.NATIVEWIND_OS = ["android", "ios"].includes(this.environment.name) ? "native" : "web"
  }  
}
  1. After all that, native still doesn't work, but I've think that the css is not correctly being added to JS afterall. Maybe there's another vite plugin that process css inside one (I don't have the sufficient knowledge in one codebase to understand whats going on)

ceopaludetto avatar Jan 14 '25 16:01 ceopaludetto

@natew https://github.com/onejs/one/pull/414 My branch pointing to yours

ceopaludetto avatar Jan 14 '25 16:01 ceopaludetto

@ceopaludetto thanks for all this, super helpful. I applied most of the changes, got quite a bit further it seems but hitting index.default is not defined as it starts, haven't pinpointed what's going on but it's likely some module compatibility issue.

natew avatar Jan 14 '25 21:01 natew

I'll poke on this more through the day, I didn't see the jsx runtime being swapped in the native bundle either which I thought it was before will need to check.

natew avatar Jan 14 '25 21:01 natew

Ok I have it far along now, I think properly doing everything except vxrn is messing up the bundle of nativewind in the end. despite it using interop settings its leaving some require calls untouched and they break.

natew avatar Jan 14 '25 23:01 natew

a bit more poking - i think nativewind has circular imports, took a while to diagnose. will check into that next.

natew avatar Jan 15 '25 19:01 natew

CleanShot 2025-01-15 at 10 18 10@2x

natew avatar Jan 15 '25 20:01 natew

this works in 1.1.399 but for now i am only compiling non node_modules so nativewind modules may not work, we can enable it but we have to filter out a variety of node modules or else you get breaking circular imports, we just need to make the regex better there to decide when to avoid wrapping.

natew avatar Jan 15 '25 20:01 natew