I am working on a project consisting of three parts: a Client, a Server and a Common directory which contains things I want to import from both the Client and the Server. Everything can use both JS and TS. (Thanks to the babel-typescript
preset)
Directory structure
Here is how it looks like:
root/
├── babel.config.js
├── Common/
│ ├── helper1.ts
│ ├── helper2.ts
│ ├── helper3.js
├── Client/
│ ├── src/
│ │ └── file1.js
│ └── .babelrc.js
└── Server/
├── src/
│ └── file1.js
└── .babelrc.js
Babel config files
Here is what my root/babel.config.js
looks like:
module.exports = {
presets: ["@babel/preset-typescript"],
plugins: [
["@babel/plugin-transform-for-of", { assumeArray: true }],
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-syntax-import-meta",
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-json-strings",
["@babel/plugin-proposal-decorators", { legacy: true }],
"@babel/plugin-proposal-function-sent",
"@babel/plugin-proposal-export-namespace-from",
"@babel/plugin-proposal-numeric-separator",
"@babel/plugin-proposal-throw-expressions",
"@babel/plugin-proposal-export-default-from",
"@babel/plugin-proposal-logical-assignment-operators",
"@babel/plugin-proposal-optional-chaining",
["@babel/plugin-proposal-pipeline-operator", { proposal: "minimal" }],
"@babel/plugin-proposal-nullish-coalescing-operator",
"@babel/plugin-proposal-do-expressions",
"@babel/plugin-proposal-function-bind",
],
};
And here is what my Server/.babelrc.js
looks like:
const moduleAlias = require("./tools/module-alias");
const rootConfig = require("../babel.config");
module.exports = {
presets: [
...rootConfig.presets,
[
"@babel/preset-env",
{
targets: {
node: "current",
},
exclude: ["transform-for-of"],
},
],
],
plugins: [
...rootConfig.plugins,
[
"babel-plugin-module-resolver",
{
root: ["."],
alias: moduleAlias.relativeAliases,
extensions: [".js", ".ts"],
},
],
],
};
I will omit the Client/.babelrc.js
since it's very similar to the Server one.
Basic test files
Here is an example Common/helper3.js
file:
function doubleSay(str) {
return `${str}, ${str}`;
}
function capitalize(str) {
return str[0].toUpperCase() + str.substring(1);
}
function exclaim(str) {
return `${str}!`;
}
const result = "hello" |> doubleSay |> capitalize |> exclaim;
console.log(result);
And inside Server/src/index.js
I just import the file Common/helper3.js
.
The error
Then, inside the Server
directory, I do this:
npx babel-node src/index.js -x .ts,.js
Which prints the following error:
const result = "hello" |> doubleSay |> capitalize |> exclaim;
^
SyntaxError: Unexpected token >
I am definitely sure this error is related to my "strange" directory structure since it's fine when I put this exact file under Server/src
.
The question
How can I keep this directory structure and tell Babel to use a config when it processes files within the Common
directory?
I don't use Lerna or anything. I have setup special aliases that resolve $common
to ../Common
where needed. I know there is no issue with this since the file is properly found by Babel (otherwise I would get a "File not found" error)
Note
This babel
structure is one of my attempt to fix the issue above. Originally I only had one babel.config.js
inside Server
and another inside Client
. I thought having one at the root would solve this problem but it didn't change anything.
Edit after searching a lot more:
After taking a look at the babel code to find the config parsing, I noticed that this line : https://github.com/babel/babel/blob/8ca99b9f0938daa6a7d91df81e612a1a24b09d98/packages/babel-core/src/config/config-chain.js#L456 is called (null
is returned).
I printed everything in this scope and noticed that babel automatically generates an only
parameter containing the cwd. (Effectively saying that my babel.config.js
doesn't affect my common
directory despite being "above" is in the directory hierarchy).
I decided to try overloading it in the command line and arrived at this command:
npx babel-node src/index.js --root-mode upward -x .ts,.js --only .,../Common/ --ignore node_modules
(Added --only
and --ignore
)
This made me progress a bit: instead of failing to parse advanced syntax (pipeline operator) in js files, it failed on a ts failing, saying
export const accountStatus = Object.freeze({
^^^^^^
SyntaxError: Unexpected token export
What I don't understand is how it can parse the pipeline operator but not the typescript file even though both the pipeline plugin and the typescript are inside the same babel.config.js
Edit after solving this last issue:
Adding --only
and --ignore
made it work. The other issue was because I forgot to add the @babel/plugin-transform-modules-commonjs
plugin and it was not able to resolve the import.
The slight change I did was adding ignore: ["**/node_modules"],
to my root babel.config.js
file and change my command to use those arguments: --root-mode upward -x .ts,.js --ignore __fake__
.
Adding a random --ignore
is enough to prevent babel
from guessing by itself.
This is the solution I use and it works fine even though it's not very elegant.