SWC with JavaScript: How to handle CSS imports and how to absolute imports?
Asked Answered
A

3

2

TL;DR

  • How can you tell SWC to compile CSS files imported in React components?
  • How can you tell SWC to compile absolute imports in tests and in React components?

Here is a minimal reproducible example.


Context

We're migrating from Babel to SWC. (I asked a question a little while ago. I'm improving on that question's answer.)

We're migrated the command from:

"test": "NODE_ENV=test riteway -r @babel/register 'src/**/*.test.js' | tap-nirvana",

to

"test": "SWC_NODE_PROJECT=./jsconfig.json riteway -r @swc-node/register src/**/*.test.js | tap-nirvana",

where the jsconfig.json looks like this:

{
  "compilerOptions": {
    "allowJs": true,
    "baseUrl": "./src",
    "jsx": "react-jsx"
  }
}

If we write try to compile a test for a self-contained component (no absolute imports, no CSS) it works:

import { describe } from 'riteway';
import render from 'riteway/render-component';

function HomePageComponent({ user: { email } }) {
  return <p>{email}</p>;
}

describe('home page component', async assert => {
  const user = { email: 'foo' };
  const $ = render(<HomePageComponent user={user} />);

  assert({
    given: 'a user',
    should: 'render its email',
    actual: $('p').text(),
    expected: user.email,
  });
});

The test compiles fine.

With Babel we had a .babelrc like this:

{
  "env": {
    "test": {
      "plugins": [
        [
          "module-resolver",
          {
            "root": [
              "."
            ],
            "alias": {
              "components": "./src/components",
              "config": "./src/config",
              "features": "./src/features",
              "hocs": "./src/hocs",
              "hooks": "./src/hooks",
              "pages": "./src/pages",
              "redux": "./src/redux",
              "styles": "./src/styles",
              "tests": "./src/tests",
              "utils": "./src/utils"
            }
          }
        ]
      ]
    }
  },
  "presets": [
    [
      "next/babel",
      {
        "ramda": {}
      }
    ]
  ],
  "plugins": [
    ["styled-components", { "ssr": true }]
  ]
}

Where the styles where taken care of by styled-components and the absolute imports where defined via the module-resolver plugin. (We switched away from styled-components to CSS modules, which is why we import from .module.css CSS files. Anyways ...)

If we write the test how we wanted to write it with their actual imports like this:

import { describe } from 'riteway';
import render from 'riteway/render-component';
import { createPopulatedUserProfile } from 'user-profile/user-profile-factories';

import HomePageComponent from './home-page-component';

describe('home page component', async assert => {
  const user = createPopulatedUserProfile();
  const $ = render(<HomePageComponent user={user} />);

  assert({
    given: 'a user',
    should: 'render its email',
    actual: $('p').text(),
    expected: user.email,
  });
});

It fails with:

$ SWC_NODE_PROJECT=./jsconfig.json riteway -r @swc-node/register src/features/home/home-page-component.test.js | tap-nirvana
/Users/janhesters/dev/my-project/src/features/home/home.module.css:1
(function (exports, require, module, __filename, __dirname) { .container {
                                                              ^

SyntaxError: Unexpected token '.'

when we leave in the CSS import in home-page-component.js, or with:

$ SWC_NODE_PROJECT=./jsconfig.json riteway -r @swc-node/register src/features/home/home-page-component.test.js | tap-nirvana
node:internal/modules/cjs/loader:936
  throw err;
  ^

Error: Cannot find module 'user-profile/user-profile-factories'
Require stack:
- /Users/janhesters/dev/my-project/src/features/home/home-page-component.test.js
- /Users/janhesters/dev/my-project/node_modules/riteway/bin/riteway

respectively, when we get rid of the CSS import.

How can we help SWC understand CSS (or mock CSS modules) and how can we help it understand absolute imports?

We already set the baseUrl in jsconfig.json ...

Aleshia answered 28/1, 2022 at 19:58 Comment(7)
Hi, I would like to help you. If it's possible, can you create a github project for that? minimal one..Counterespionage
@AlonShmiel Thank you sooo much! I created it here: github.com/janhesters/riteway-swc-exampleAleshia
I will check it and let you knowCounterespionage
Hi, I didn't succeed to solve your issue. I tried to change jsconfig to: { "compilerOptions": { "allowJs": true, "baseUrl": ".", "jsx": "react-jsx", // "target": "esnext", // "module": "commonjs", "paths": { "@utils/*": ["./src/utils/*"] } }, "include": ["./src/utils/**/*"] } with no success. Can you please try it and then run: npm run test:absolute-import (In addition, I added type: module in package.json)Counterespionage
Hi @AlonShmiel, I tried it and it didn't work neither. Thanks for trying to help! 🙏Aleshia
Have you checked this thread? github.com/ericelliott/riteway/issues/149Lu
@shubhamjha Yup, I'm the author of those issues haha That was with Babel, but this question is about SWC.Aleshia
A
1

I was able to solve all issues without writing any plugins. I pushed the solution to my example repo from the question.

Firstly, use the official SWC project's register. This means, you have to compile your tests like this:

"test": "riteway -r @swc/register 'src/**/*.test.js' | tap-nirvana",

You can install it by running:

yarn add --dev @swc/core @swc/register

We need this version of register, because it takes into account an .swcrc file, which the other one did not.

Secondly, you need both "path" and "baseUrl" to fix the absolute imports because for some reason SWC doesn't infer all paths with only a "baseUrl" provided. And you need to adapt your .swcrc to handle React.

{
  "jsc": {
    "baseUrl": "./src",
    "paths": {
      "*.css": ["utils/identity-object-proxy.js"],
      "utils/*": ["utils/*"]
    },
    "parser": {
      "jsx": true,
      "syntax": "ecmascript"
    },
    "transform": {
      "react": {
        "runtime": "automatic"
      }
    }
  },
  "module": {
    "type": "commonjs"
  }
}

Thirdly, to solve .css you need to remap all imports to .css files to an identity object proxy, which you can see in the .swcrc example above. An identity object proxy is an object that, when you reference any property, returns the stringified key that you're trying to reference. You can create one yourself like this:

const identityObjectProxy = new Proxy(
  {},
  {
    get: function getter(target, key) {
      if (key === '__esModule') {
        return false;
      }
      return key;
    },
  },
);

export default identityObjectProxy;

For the .css remap to go into effect you need to make all your imports to .css files absolute imports. (import styles from './styles.module.css won't work!)

Aleshia answered 21/2, 2022 at 19:55 Comment(0)
L
2

About absolute path

You already add baseUrl in the jsconfig.json file but didn't add the paths, you should modify your config file like mine:

{
  "compilerOptions": {
    "baseUrl": "./src",
    "paths": {
      "@screens": ["./screens"],
      "@shared": ["./shared"],
      "@shared/*": ["./shared/*"]
    },

The paths are the alias of module-resolver, and I guess your root shouldn't be ".", it should be exactly like your jsconfig.json file, I mean the baseUrl value.

  "plugins": [
    [
      "module-resolver",
      {
        "root": ["./src"],
        "extensions": [".ts", ".tsx", ".js", ".jsx", ".json"],
        "alias": {
          "@screens": "./src/screens",
          "@shared": "./src/shared"
        }
      }
    ]
  ],

If you have Webpack, you should have alias config in your resolve key off Webpack config object, like mine:

const resolve = {
  extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
  alias: {
    '@screens': path.join(__dirname, 'src/screens/'),
    '@shared': path.join(__dirname, 'src/shared/'),
  },
  modules: ['src', 'node_modules'],
  descriptionFiles: ['package.json'],
};

About CSS

Actually, you are using CSS file as CSS-Modules not like recommended NexJS doc, in docs developer should import CSS like import './styles.css' and then use it as string in JSX like <div className="main"

But

You are importing it like a module (CSS-Module):

// you did it

import styles from './styles.css';

<div className={styles.main}

As you know it is built-in support by this doc, it is supported by NextJS, but SWC cannot understand it. I put in hours to find a way for it, but it seems SWC doesn't support CSS-Module yet. you should create your own plugin for SWC to support CSS-Module.

Liegnitz answered 3/2, 2022 at 0:43 Comment(0)
A
1

I was able to solve all issues without writing any plugins. I pushed the solution to my example repo from the question.

Firstly, use the official SWC project's register. This means, you have to compile your tests like this:

"test": "riteway -r @swc/register 'src/**/*.test.js' | tap-nirvana",

You can install it by running:

yarn add --dev @swc/core @swc/register

We need this version of register, because it takes into account an .swcrc file, which the other one did not.

Secondly, you need both "path" and "baseUrl" to fix the absolute imports because for some reason SWC doesn't infer all paths with only a "baseUrl" provided. And you need to adapt your .swcrc to handle React.

{
  "jsc": {
    "baseUrl": "./src",
    "paths": {
      "*.css": ["utils/identity-object-proxy.js"],
      "utils/*": ["utils/*"]
    },
    "parser": {
      "jsx": true,
      "syntax": "ecmascript"
    },
    "transform": {
      "react": {
        "runtime": "automatic"
      }
    }
  },
  "module": {
    "type": "commonjs"
  }
}

Thirdly, to solve .css you need to remap all imports to .css files to an identity object proxy, which you can see in the .swcrc example above. An identity object proxy is an object that, when you reference any property, returns the stringified key that you're trying to reference. You can create one yourself like this:

const identityObjectProxy = new Proxy(
  {},
  {
    get: function getter(target, key) {
      if (key === '__esModule') {
        return false;
      }
      return key;
    },
  },
);

export default identityObjectProxy;

For the .css remap to go into effect you need to make all your imports to .css files absolute imports. (import styles from './styles.module.css won't work!)

Aleshia answered 21/2, 2022 at 19:55 Comment(0)
H
0
  1. How can we help SWC understand CSS (or mock CSS modules)? - SWC doesn't understand css natively, and neither did Babel. As you noted, when you were using Babel, the plugin styled-components took care of this. You'll need to do the same with SWC. I can't find an existing SWC plugin that does this, but you can roll your own. Obviously this is a pain, but such is the cost of using new tooling.
  2. How can we help SWC understand absolute imports? - The .swrc options for baseUrl and paths should do what you want, but that, too, seems to have some issues.

You may have better luck creating issues directly in the @swc-node GitHub repo, but given the comments there it feels like you might be SOL for a while. Might be faster/easier to rewrite your tests using one of the libraries that Next supports out of the box.

Hocuspocus answered 31/1, 2022 at 22:53 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.