Yarn PnP & SWC Plugin Resolution Bug: A Deep Dive

by Omar Yusuf 50 views

Hey everyone, let's dive into a tricky issue I encountered while trying to use @swc/plugin-formatjs in my React library with rslib. I'm running into a module resolution error specifically when using Yarn PnP (Plug'n'Play). It's a bit of a head-scratcher, so I wanted to share the details and see if anyone has some insights or solutions.

The Problem: Module Resolution Failure with Yarn PnP

Specifically, the error message I'm getting is:

├─▶ Failed to compile wasm plugins
├─▶ failed to resolve plugin path: @swc/plugin-formatjs
╰─▶ failed to get the node_modules path

This error occurs during the build process, indicating that the system is unable to locate the @swc/plugin-formatjs plugin when using Yarn PnP. Let's break down why this might be happening and what I've tried so far.

Understanding Yarn PnP and Module Resolution

First, let's quickly recap what Yarn PnP is and how it differs from the traditional node_modules approach. Yarn PnP is a strategy that eliminates the node_modules directory by storing dependencies in a single location and using a .pnp.cjs file to map dependencies to their locations. This method offers several advantages, such as faster installation times and reduced disk space usage. However, it also requires that tools and libraries correctly implement PnP support to resolve modules.

In a standard node_modules setup, Node.js searches for modules in node_modules directories up the file system tree until it finds the required package. With PnP, this traditional lookup mechanism is bypassed, and the .pnp.cjs file is used to directly locate packages. If a tool doesn't support PnP, it might fail to resolve modules correctly, leading to errors like the one I'm experiencing.

Diving into the Configuration

Here’s the rslib.config.ts configuration I’m using. This file defines how my React library is built using rslib.

import { pluginReact } from '@rsbuild/plugin-react';
import { defineConfig } from '@rslib/core';

export default defineConfig({
 source: {
 entry: {
 index: ['./src/**'],
 },
 },
 lib: [
 {
 bundle: false,
 dts: true,
 format: 'esm',
 },
 ],
 output: {
 target: 'web',
 },
 plugins: [pluginReact()],
 tools: {
 swc: {
 jsc: {
 experimental: {
 plugins: [
 [
 '@swc/plugin-formatjs',
 {
 idInterpolationPattern: '[sha512:contenthash:base64:6]',
 ast: true,
 },
 ],
 ],
 },
 },
 },
 },
});

Let's walk through the key parts of this configuration:

  • entry: Specifies the entry points for the library, in this case, all files under the src directory.
  • lib: Configures the library build, setting bundle to false, enabling declaration file generation (dts), and setting the format to ES modules (esm).
  • output: Sets the target environment to web.
  • plugins: Includes the @rsbuild/plugin-react plugin for React support.
  • tools.swc: Configures the SWC (Speedy Web Compiler) settings. This is where the @swc/plugin-formatjs plugin is specified.
  • tools.swc.jsc.experimental.plugins: This is the crucial part. It tells SWC to use the @swc/plugin-formatjs plugin with specific options. The idInterpolationPattern and ast options are used to configure how message IDs are generated and whether the AST (Abstract Syntax Tree) should be used.

The configuration seems correct, and the plugin is properly specified within the SWC settings. So, why is it failing to resolve the plugin path when using Yarn PnP?

Temporary Workaround: nodeLinker: node-modules

Interestingly, I found a workaround: setting nodeLinker: node-modules in .yarnrc.yml. This tells Yarn to use the traditional node_modules structure instead of PnP. When I do this, the error disappears, and the build completes successfully. However, this isn't a long-term solution because I want to leverage the benefits of PnP.

nodeLinker: node-modules

Reproducing the Issue

To help illustrate the problem, I've created a minimal repository that reproduces the bug. You can find it here: https://github.com/neemski/rslib-swc-formatjs-pnp-bug.

Here are the steps to reproduce the issue:

  1. Clone the repository.
  2. Run yarn install to install dependencies.
  3. Run yarn build to build the library.

If you have Yarn PnP enabled (which is the default in Yarn 2+), you should see the error I described earlier. If you switch to nodeLinker: node-modules, the build should succeed.

Similar Issue in Rspack

I also found a similar issue reported in the Rspack repository: https://github.com/web-infra-dev/rspack/issues/9858. This suggests that the problem might be related to how SWC plugins are resolved in PnP environments across different tools in the web-infra-dev ecosystem.

Potential Causes and Solutions

So, what could be causing this issue? Here are a few potential causes and some possible solutions:

  1. SWC PnP Compatibility: The core issue might be that SWC or the specific plugin @swc/plugin-formatjs doesn't fully support Yarn PnP. This could mean that SWC's module resolution logic isn't correctly using the PnP API to find the plugin.

    • Solution: Check the SWC documentation and issue tracker for any known issues related to PnP. If there's no existing issue, consider opening one to report the problem. Also, ensure you're using the latest versions of SWC and the plugin, as updates might include PnP compatibility fixes.
  2. rslib PnP Integration: It's possible that rslib itself has some compatibility issues with PnP, especially in how it handles SWC plugins. If rslib isn't correctly passing the PnP context to SWC, the plugin might fail to resolve.

    • Solution: Investigate rslib's PnP support. Look for any configuration options or known issues related to PnP. Again, checking the documentation and issue tracker is a good starting point.
  3. Plugin Configuration: While the configuration looks correct, there might be a subtle issue in how the plugin is being configured within rslib. For example, the plugin path might not be correctly resolved in the PnP environment.

    • Solution: Try explicitly specifying the full path to the plugin in the configuration. This might help bypass the module resolution issue. You can use require.resolve to get the absolute path of the plugin:
    const formatjsPluginPath = require.resolve('@swc/plugin-formatjs');
    
    // ...
    plugins: [
    

[ formatjsPluginPath, idInterpolationPattern '[sha512:contenthash:base64:6]', ast: true, , ], ], // ... ```

  1. Yarn PnP Limitations: There might be some limitations or edge cases in Yarn PnP that are causing this issue. PnP is a complex system, and not all tools and libraries are fully compatible yet.

    • Solution: Check the Yarn documentation and issue tracker for any known limitations or workarounds related to SWC plugins. You might also consider trying different Yarn versions to see if the issue is specific to a particular version.

Next Steps and Call for Help

I've spent a fair bit of time trying to figure this out, but I'm still stuck. I'm hoping someone in the community has encountered a similar issue or has some ideas on how to resolve it. If you have any insights, suggestions, or potential solutions, please let me know!

In the meantime, I'll continue to investigate and try different approaches. I'll also be sure to update this discussion with any progress I make.

Thanks for your help, everyone!

Additional Information

Here's some additional information about my environment:

System:
 OS: macOS 15.5
 CPU: (8) arm64 Apple M1 Pro
 Memory: 1.72 GB / 32.00 GB
 Shell: 5.9 - /bin/zsh
Browsers:
 Brave Browser: 139.1.81.135
 Chrome: 139.0.7258.128
 Safari: 18.5

This information might be helpful in identifying any environment-specific issues.