callstack / react-native-builder-bob

👷‍♂️ Simple set of CLIs to scaffold and build React Native libraries for different targets
https://callstack.github.io/react-native-builder-bob/
2.74k stars 182 forks source link

`react-native-builder-bob >= 0.28.0` generate `package.json` in the `lib` with `{"type":"module"}` content #621

Closed mskorenkyi closed 1 week ago

mskorenkyi commented 1 week ago

Description

After upgrading the version of the react-native-builder-bob to the latest version, I encountered an issue where the builder started generating a package.json file with content {"type":"module"} in the output directory (in my case, in lib). Upon investigation, I discovered that these changes were responsible for this behavior.

Unfortunately, I was unable to find any option to prevent the creation of the package.json in the output directory. The only solution I found was to manually remove it from my end after the build was completed by react-native-builder-bob.

Packages

Selected options

Below is my react-native-builder-bob config.

{
    "source": "src",
    "output": "lib",
    "targets": [
      "module",
      "typescript"
    ]
  }

Link to repro

https://github.com/mskorenkyi/BobIssue/

Environment

System:   OS: macOS 14.2.1   CPU: (8) arm64 Apple M1 Pro

Binaries:   Node:     version: 20.12.2     path: ~/.nvm/versions/node/v20.12.2/bin/node   Yarn:     version: 1.22.22     path: ~/.nvm/versions/node/v20.12.2/bin/yarn   npm:     version: 10.8.1     path: ~/.nvm/versions/node/v20.12.2/bin/npm

satya164 commented 1 week ago

What's the issue? Can you describe the scenario it is breaking so it can be addressed appropriately?

mskorenkyi commented 1 week ago

@satya164 Thanks for your question.

The scenario is next: I'm currently working on developing and publishing my private React Native material design package. To build the library and publish it, I'm using react-native-builder-bob. For local development, I'm using webpack, and I have configured the webpack to point to the lib directory rather than the src folder. Occasionally, I need to bundle my code into a single bundle.js file for the project's requirements, which is also done using webpack.

After updating react-native-builder-bob to the latest version, webpack started throwing numerous errors, one of which is:

ERROR in ./lib/module/index.web.js
Module not found: Error: Can't resolve './components/Button' in '~/lib/module'
Did you mean 'Button.js'?
BREAKING CHANGE: The request './components/Button' failed to resolve only because it was resolved as fully specified
(probably because the origin is strict EcmaScript Module, e. g. a module with javascript mimetype, a '*.mjs' file, or a '*.js' file where the package.json contains '"type": "module"').
The extension in the request is mandatory for it to be fully specified.
Add the extension to the request.

As I described earlier, that this issue arises due to react-native-builder-bob generating a package.json file with the content {"type":"module"} in the lib/module directory, starting from version 0.28.0 and there are no any config option to avoid that. Manually removing this file resolves the issue and allows webpack to work without errors.

satya164 commented 1 week ago

I recommend setting fullySpecified to false in your loader options https://webpack.js.org/configuration/module/

While removing the type: module may work for this specific case, modules with ESM basically break without it for React Native as without it platform specific extensions don't work.

mskorenkyi commented 1 week ago

I recommend setting fullySpecified to false in your loader options https://webpack.js.org/configuration/module/

While removing the type: module may work for this specific case, modules with ESM basically break without it for React Native as without it platform specific extensions don't work.

Unfortunately, fullySpecified does not work for me. I'd tried it before creating this ticket.

ERROR in ./lib/module/index.web.js 120:0-66
Module not found: Error: Can't resolve './components/Button' in '~/lib/module'
Did you mean 'Button.js'?
BREAKING CHANGE: The request './components/Button' failed to resolve only because it was resolved as fully specified
(probably because the origin is strict EcmaScript Module, e. g. a module with javascript mimetype, a '*.mjs' file, or a '*.js' file where the package.json contains '"type": "module"').
The extension in the request is mandatory for it to be fully specified.
Add the extension to the request.

I completely agree that removing type: module is risky and may cause some issues. My suggestion is to have an option (e.g., generatePackageType or something similar) that would control the generation of type: module. By default, this option would generate type: module to avoid breaking changes. However, in my case, I would like to be able to turn this option off in order not to generate that.

satya164 commented 1 week ago

I recommend setting fullySpecified to false in your loader options https://webpack.js.org/configuration/module/

While removing the type: module may work for this specific case, modules with ESM basically break without it for React Native as without it platform specific extensions don't work.

Unfortunately, fullySpecified does not work for me. I'd tried it before creating this ticket.


ERROR in ./lib/module/index.web.js 120:0-66

Module not found: Error: Can't resolve './components/Button' in '~/lib/module'

Did you mean 'Button.js'?

BREAKING CHANGE: The request './components/Button' failed to resolve only because it was resolved as fully specified

(probably because the origin is strict EcmaScript Module, e. g. a module with javascript mimetype, a '*.mjs' file, or a '*.js' file where the package.json contains '"type": "module"').

The extension in the request is mandatory for it to be fully specified.

Add the extension to the request.

I completely agree that removing type: module is risky and may cause some issues. My suggestion is to have an option (e.g., generatePackageType or something similar) that would control the generation of type: module. By default, this option would generate type: module to avoid breaking changes. However, in my case, I would like to be able to turn this option off in order not to generate that.

The error says that fullySpecified is enabled. It should be disabled. Can you share your configuration?

Adding an option specifically for this won't fix the actual issue - webpack isn't configured properly for React Native libs. Your configuration will still break for other React Native libraries.

Our goal is to have a consistent setup that works for all React Native libs - so we can standardize on the bundler configuration for everyone. Adding options that alter how the bundler needs to be configured makes this goal harder to achieve.

So we'd either remove this entirely if it's not possible to have a bundler configuration that works properly with this setup, or fix the bundler setups to be aligned.

If your library isn't for React Native specifically and hence you don't want to disable fullySpecified, you can enable the esm option in Bob's config which will output fully specified import paths. However, it will not work when using platform specific extensions. For platform specific extensions to work, fullySpecified needs to be disabled.

mskorenkyi commented 1 week ago

I recommend setting fullySpecified to false in your loader options https://webpack.js.org/configuration/module/

While removing the type: module may work for this specific case, modules with ESM basically break without it for React Native as without it platform specific extensions don't work.

Unfortunately, fullySpecified does not work for me. I'd tried it before creating this ticket.


ERROR in ./lib/module/index.web.js 120:0-66

Module not found: Error: Can't resolve './components/Button' in '~/lib/module'

Did you mean 'Button.js'?

BREAKING CHANGE: The request './components/Button' failed to resolve only because it was resolved as fully specified

(probably because the origin is strict EcmaScript Module, e. g. a module with javascript mimetype, a '*.mjs' file, or a '*.js' file where the package.json contains '"type": "module"').

The extension in the request is mandatory for it to be fully specified.

Add the extension to the request.

I completely agree that removing type: module is risky and may cause some issues. My suggestion is to have an option (e.g., generatePackageType or something similar) that would control the generation of type: module. By default, this option would generate type: module to avoid breaking changes. However, in my case, I would like to be able to turn this option off in order not to generate that.

The error says that fullySpecified is enabled. It should be disabled. Can you share your configuration?

Adding an option specifically for this won't fix the actual issue - webpack isn't configured properly for React Native libs. Your configuration will still break for other React Native libraries.

Our goal is to have a consistent setup that works for all React Native libs - so we can standardize on the bundler configuration for everyone. Adding options that alter how the bundler needs to be configured makes this goal harder to achieve.

So we'd either remove this entirely if it's not possible to have a bundler configuration that works properly with this setup, or fix the bundler setups to be aligned.

If your library isn't for React Native specifically and hence you don't want to disable fullySpecified, you can enable the esm option in Bob's config which will output fully specified import paths. However, it will not work when using platform specific extensions. For platform specific extensions to work, fullySpecified needs to be disabled.

My library is developing for react-native and react-native-web.

Below is my webpack config


// ~root/scripts/webpack.config.js

const appDirectory = path.resolve(__dirname, '../');

module.exports = {
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        include: [
          ...
          path.resolve(appDirectory, 'node_modules/react-native-vector-icons'),
          ...
        ],
        resolve: {
          fullySpecified: false,
        },
        use: {
          loader: 'babel-loader',
          options: {
            cacheDirectory: true,
            presets: ['module:@react-native/babel-preset'],
            plugins: ['react-native-web'],
          },
        },
      },
      {
        test: /\.(gif|jpe?g|png|svg)$/,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name].[ext]',
          },
        },
      },
      {
        test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        include: [
          path.resolve(
            appDirectory,
            'node_modules/react-native-vector-icons/Fonts'
          ),
          path.resolve(appDirectory, 'lib/module/assets/fonts'),
        ],
        use: [
          {
            loader: 'url-loader',
          },
        ],
      },
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },

  resolve: {
    // This will only alias the exact import "react-native"
    alias: {
      'react-native$': path.resolve(__dirname, 'rnw-alias.js'),
      'react-native-linear-gradient': 'react-native-web-linear-gradient',
      'react-native-svg': 'react-native-svg-web',
    },
    // If you're working on a multi-platform React Native app, web-specific
    // module implementations should be written in files using the extension
    // `.web.js`.
    extensions: ['.web.js', '.js'],
  },
};

and build config

// ~root/scripts/build.config.js

const baseConfig = require('./webpack.config');

const appDirectory = path.resolve(__dirname, '../');

const config = Object.assign(
  {
    mode: 'production',
    entry: [path.resolve(appDirectory, 'lib/module/index.web.js')],
    externals: {
      react: 'react',
    },
    output: {
      library: 'myLibrary',
      libraryTarget: 'umd',
      filename: 'bundle.web.js',
      path: path.resolve(appDirectory, 'dist'),
    },
  },
  baseConfig
);

module.exports = config;
satya164 commented 1 week ago

@mskorenkyi I don't see your full include statement but I assume that the folder containing the library code is not included, so fullySpecified: false, doesn't get applied. You can try adding another rule under rules:

{
  test: /\.(js|jsx)$/,
  include: regex_for_library_path,
  resolve: {
    fullySpecified: false,
  },
},

For app code, webpack configs should include the following so libraries with ESM code can work:

{
  test: /\.(js|jsx)$/,
  include: /node_modules/,
  resolve: {
    fullySpecified: false,
  },
},

Here is a repo with a minimal webpack config that demonstrates this https://github.com/satya164/bob-webpack-example

To test, run yarn prepare, then yarn start.