Here is a minimal reproducible example.
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
...
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!)