storybookjs / addon-react-native-web

Build react-native-web projects in Storybook for React
MIT License
83 stars 24 forks source link

[doc] How to configure for NativeWind #45

Closed jbtheard closed 9 months ago

jbtheard commented 1 year ago

NativeWind allows to use tailwind css across platforms, leveraging react-native Stylesheet for mobile and acting as a compatibility layer with Tailwind on web. It's a replacement to tailwinds-react-native and is used in more and more projects such as Solito for their Tailwind starter kit.

As we test components across platforms, a guide on how to implement it for Storybook would come handy

dannyhw commented 1 year ago

Yeah that could be cool, actually its not very hard to setup I could write something but I think there are some details to figure out on that before I do. However there is an example here:

https://github.com/dohomi/nativewind-solito-kitchen-sink/blob/master/apps/storybook-react/.storybook/main.ts

jbtheard commented 1 year ago

Oh I didn't identify that one in my search. I'll have a look at it first. Thanks for pointing at it. (and thanks for this package)

kimchouard commented 9 months ago

Alright, reviving this thread since I can't get @dannyhw's expo-template-storybook to work with NativeWind. 😬

Here's a boilerplate I'm trying to put together: expo-nativewind-template-storybook 👨🏼‍🍳

👉 It works great with yarn storybook, both on native and web (styles are properly picked up) ❗️ BUT fails with yarn storybook (using the React Native Web Addon through webpack) with this error:

CleanShot 2024-02-07 at 16 08 08@2x

Here's my .storybook/main.js config:

import path from 'path';

/** @type{import("@storybook/react-webpack5").StorybookConfig} */
module.exports = {
  stories: [
    "../components/**/*.stories.mdx",
    "../components/**/*.stories.@(js|jsx|ts|tsx)",
  ],
  addons: [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    {
      name: '@storybook/addon-react-native-web',
      options: {
        modulesToTranspile: [
          'nativewind',
          // 'react-native-css-interop', // Needed?
          'react-native-reanimated',
        ],
        babelPlugins: [
          '@babel/plugin-proposal-export-namespace-from',
          'react-native-reanimated/plugin',
          // 👇 This is what makes it crash 👇 
          'nativewind/babel', 
        ],
      },
    },
  ],
  framework: {
    name: "@storybook/react-webpack5",
    options: {},
  },
  docs: {
    autodocs: true,
  },
  webpackFinal: async (config, { configType }) => {
    config.module.rules.push({
      test: /\.css$/,
      use: [
        {
          loader: 'postcss-loader',
          options: {
            postcssOptions: {
              plugins: [require('tailwindcss'), require('autoprefixer')]
            }
          }
        }
      ],
      include: path.resolve(__dirname, '../')
    })

    return {
      ...config
    }
  }
};

Feel free to clone the repo and test for yourself! Looking forward to get your take on this @dannyhw 🤔 Thanks for all your work to date to bring Storybook to the RN community 🙌🏻

Notes:

dannyhw commented 9 months ago

You should import global css in your preview.js but i can take a look, seems like theres an issue with a plugin too 🤔

dannyhw commented 9 months ago

@kimchouard "nativewind/babel" is a preset and not a plugin thats why you get that error, I can provide the option to add presets.

kimchouard commented 9 months ago

You should import global css in your preview.js but i can take a look, seems like theres an issue with a plugin too 🤔

Indeed, this is done here already:

import "../global.css"

@kimchouard "nativewind/babel" is a preset and not a plugin thats why you get that error

Yes, it looked odd to me but it was in the main.ts example shared previously.

        babelPlugins: [
          '@babel/plugin-proposal-export-namespace-from',
          'react-native-reanimated/plugin',
          // 'nativewind/babel', ➡️ Need to be moved to a new `babelPresets` option?
        ],

☝️ If you simply comment the 'nativewind/babel' line, the web server will run smoothly but the Nativewind styles won't be picked up... Maybe due to another babel setting missing? 🤷🏼‍♂️


I can provide the option to add presets.

It would be GREAT to be able to set babelPresets in the @storybook/addon-react-native-web options indeed, I think this might be the issue! 👉 If you look at line 5 (and 6) from the babel.config.js there is a specific NativeWind preset configuration that needs to be added:

    presets: [
      ["babel-preset-expo", { jsxImportSource: "nativewind" }],
      "nativewind/babel",
    ],

⚠️ That means that we would need to be able to add babelPresets in the form of string OR array.

Thanks for looking into it! 😎

dannyhw commented 9 months ago

@kimchouard just got everything working in #81 but need some time to figure out a few details then you can pass all the needed options

dannyhw commented 9 months ago

@kimchouard update to 0.0.23 and this kind of config should work

const path = require('path');

/** @type{import("@storybook/react-webpack5").StorybookConfig} */
export default {
  stories: [
    '../stories/**/*.stories.mdx',
    '../stories/**/*.stories.@(js|jsx|ts|tsx)', // your path here
  ],
  addons: [
    {
      name: '@storybook/addon-react-native-web',
      options: {
        modulesToTranspile: [
          'react-native-reanimated',
          'nativewind',
          'react-native-css-interop',
        ],
        babelPresets: ['nativewind/babel'],
        babelPresetReactOptions: { jsxImportSource: 'nativewind' },
        babelPlugins: [
          'react-native-reanimated/plugin',
        ],
      },
    },
    '@storybook/addon-essentials',
  ],
  framework: {
    name: '@storybook/react-webpack5',
    options: {},
  },
  webpackFinal: async (config) => {
    config.module?.rules?.push({
      test: /\.css$/,
      use: [
        {
          loader: 'postcss-loader',
          options: {
            postcssOptions: {
              plugins: [require('tailwindcss'), require('autoprefixer')],
            },
          },
        },
      ],
      include: [
        path.resolve(__dirname, '../'), // path to project root
      ],
    });

    return config,
  },
};
dannyhw commented 9 months ago

there should be a way to make this work with the storybook styling addon instead of adding a postcss-loader manually but I haven't got around to trying that out

dannyhw commented 9 months ago

There is also now a nativewind setup in this repo if you want to see a working setup

You can see it working here

and you can find the example config here https://github.com/storybookjs/addon-react-native-web/blob/main/.storybook/main.js

kimchouard commented 9 months ago

@dannyhw YOU'RE THE MAN!!! 🙌🏻

FYI, I got it to work but I had to add an extra babelPlugin:

        babelPlugins: [
          // (...)
          [
            '@babel/plugin-transform-react-jsx',
            {
              runtime: 'automatic',
              importSource: 'nativewind',
            },
          ],
        ],

Not sure what's different in your example that makes it work without this plugin configuration? 🤷🏼‍♂️

kimchouard commented 9 months ago

I've updated the expo-nativewind-template-storybook boilerplate to match.

@dannyhw I think you can safely close this issue now. :)

Merci!

kimchouard commented 9 months ago

Since this issue is called [doc] ..., I've created #85 to add the working config to the doc. :)

dannyhw commented 9 months ago

I believe this is now completed, please let me know if more information is needed and you want to reopen.

rwitchell commented 2 hours ago

for anyone else trying to get Expo v50+ and NativeWind v4+ and Storybook v8.3+ working:

Tip: Use the web version of storybook to receive compilation and runtime errors that may not bubble up from the mobile simulators.

// .ondevice/main.ts 
// AND/OR .storybook/main.ts

module.exports = {
  addons: [
    /*existing addons,*/
    {
      name: '@storybook/addon-react-native-web',
      options: {
        modulesToTranspile: [
          'react-native-reanimated',
          'nativewind',
          'react-native-css-interop',
        ],
        babelPresetReactOptions: { jsxImportSource: 'nativewind' },
        // If you have a bable.config.js file: this can also be placed there, and removed from here
        babelPresets: ['nativewind/babel'],
        babelPlugins: [
          // If you have a bable.config.js file: this can also be placed there, and removed from here
          'react-native-reanimated/plugin',
          // If you have a bable.config.js file: this can also be placed there, and removed from here
           [
            '@babel/plugin-transform-react-jsx',
            {
              runtime: 'automatic',
              importSource: 'nativewind',
            },
           ],
         ],
      },
    },
  ],
};
// .ondevice/preview.tsx
// add the following
import '../styles/global.css'
// metro.config.js
const path = require("path");
const { getDefaultConfig } = require("expo/metro-config");
const { withNativeWind } = require("nativewind/metro");
const withStorybook = require("@storybook/react-native/metro/withStorybook");

const defaultConfig = getDefaultConfig(__dirname);

// 👇 important: nativeWindConfig is defined and passed to withStorybook!
// replace this: module.exports = withNativeWind(config, { input: "./global.css" });
// with the below (and update your input):
const nativeWindConfig = withNativeWind(defaultConfig, { input: "./styles/global.css" });

module.exports = withStorybook(nativeWindConfig, {
  // this line helps you switch between app and storybook using variable in package.json script,
  // and changes to your app's main entry point.
  enabled: process.env.EXPO_PUBLIC_STORYBOOK_ENABLED === "true",
  configPath: path.resolve(__dirname, "./.ondevice"),
  onDisabledRemoveStorybook: true,
});
// babel.config.js
module.exports = function (api) {
  api.cache(true);
  return {
    presets: [
      ["babel-preset-expo", { jsxImportSource: "nativewind" }],
      "nativewind/babel",
    ],
    plugins: [
      ["babel-plugin-react-docgen-typescript", { exclude: "node_modules" }],
      'react-native-reanimated/plugin',
      [
        '@babel/plugin-transform-react-jsx',
        {
          runtime: 'automatic',
          importSource: 'nativewind',
        },
      ],
    ],
  };
};