While writing unit tests in Javascript (well, Typescript) for a browser extension, I had some rather complicated issue. I wanted to split my code into multiple files, and I knew I could just import them without issue in the browser, so I wouldn't be needing webpack or esbuild.
The first problem I had was that Chrome and Firefox work differently, and Firefox doesn't import the files if the import statement doesn't have the .js file extension. On the other hand, while the Typescript compiler works just fine if you use the .js extension instead of no extension, VS Code doesn't add it by default, so the first thing I had to do was add this setting to my settings.json file in VS Code:
{
"typescript.preferences.importModuleSpecifierEnding": "js"
}
And this worked fine in both browsers, until I started writing unit tests using Jest, because Jest couldn't find foo.ts if you import "./foo.js". To fix this, you need to add a configuration to Jest. This can be done in either your package.json file, or in a jest.config.js file. I prefer the jest.config.js file because you can write comments in it.
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
moduleNameMapper: {
// fixes importing "foo.js" failing when running the Jest tests.
// https://github.com/swc-project/jest/issues/64#issuecomment-1029753225
"^(\\.{1,2}/.*)\\.js$": "$1",
},
};
Let's understand what the code above means.
First, preset refers to the ts-jest configuration, which has to be installed separately. If you are using Jest with Typescript, I assume you already have this configured, because I'm not sure if it can work with Typescript if you don't do this.
Second, testEnvironment: "node" simply tells Jest to use the node environment instead of the browser environment (with the DOM). It seems this is the default value, so I'm not sure if this really needs to be set.
Third, and most importantly, moduleNameMapper maps imported names using a regex. The regex is fairly simple to understand:
^start of the line or string.\\this is escaping the backward slash (\), which is a reserved character in Javascript strings.\.in regex, this escapes the dot (.) which would otherwise mean any character.\.{1,2}the dot appears 1 or 2 times in sequence./this is just a slash..*any character any times.\\.js$this just means.jsappears at the end ($means end of the line).
Essentially, ./anything.js or ../anything.js will be mapped, but anything.js won't with this regex. Everything between the parentheses (()) is a group. The first group can be referred to as $1, which we see as the value of this key. Since the group doesn't include the \\.js part, all this regex does is take everything everything except the .js, excluding it from the imported name.