Replacing code for production with Babel transformation
If you are building some sort of an app, Babel is probably part of your build system. It is that thing which converts our fancy code to valid, working in a browser, JavaScript. Just recently at work I had to design a solution that swaps a class based on the environment. Or in another words, we have logic that should not reach our users. The file should be available locally and on our staging environment but not in production. A tiny Babel plugin was the cheapest solution for me so I decided to share the result.
The bit that I had to touch is the Babel configuration file which in our case is babel.config.js
. We are already using Babel plugins so our setup looks like this:
plugins: [
[
'babel-plugin-styled-components',
{ ssr: true },
],
[
'babel-plugin-module-resolver',
{
alias,
extensions: ['.js', '.jsx', '.ts', '.tsx'],
stripExtensions: ['.js', '.jsx', '.ts', '.tsx'],
},
],
'babel-plugin-jsx-strip-ext',
'babel-plugin-syntax-async-functions',
'babel-plugin-dynamic-import-node',
'@babel/plugin-transform-runtime',
'@babel/plugin-syntax-dynamic-import',
'@babel/plugin-syntax-import-meta',
'@babel/plugin-proposal-class-properties',
'@babel/plugin-proposal-json-strings',
'@babel/plugin-transform-classes'
]
To add a new plugin we don't need to define an external module. Because we are using .js
file we can simply place the plugin implementation directly into babel.config.js
. And a plugin really is a function that returns object with a visitor
map. For example:
const plugin = () => ({
visitor: {}
})
Then in that visitor
field we have to list the AST nodes that we want to modify. AST stands for abstract syntax tree. In JavaScript this is basically a tree liked object that statically represents our code. Our visitor function is called once per each node that matches the defined type. For example if we want to catch all the string literals in our file we have to use:
visitor: {
StringLiteral: (path) => {
// ...
}
}
So, it is all about seeing how your tree looks like (check out AST explorer here) and write a proper visitor. The code that I had to change was an import
statement. Something like:
import Service from 'company-lib/services/Service';
On the local and staging environment we wanted to use a slightly different version of the same service. So, the path should be changed only when building for those two environments.
The AST for such statements looks like this:
{
"type": "ImportDeclaration",
"specifiers": [
{
"type": "ImportDefaultSpecifier",
"local": {
"type": "Identifier",
"name": "Service"
}
}
],
"importKind": "value",
"source": {
"type": "StringLiteral",
"extra": {
"rawValue": "company-lib/services/Service",
"raw": "'company-lib/services/Service'"
},
"value": "company-lib/services/Service"
}
}
We want to visit a node of type ImportDeclaration
and check if the ImportDefaultSpecifier
's name is Service
and the path is company-lib/services/Service
. If that is true then we change the source
's value
field with the desired path.
Here's the final version of the plugin:
const plugin = () => ({
visitor: {
ImportDeclaration(path){
if (
path.node.specifiers[0].local.name === "Service" &&
path.node.source.value === 'company-lib/services/Service'
) {
path.node.source.value = './local/version/Service';
}
}
}
});
// Other Babel options
...
plugins: [
plugin,
[
'babel-plugin-styled-components',
{ ssr: true },
],
[
'babel-plugin-module-resolver',
{
alias,
extensions: ['.js', '.jsx', '.ts', '.tsx'],
stripExtensions: ['.js', '.jsx', '.ts', '.tsx'],
},
],
...
]
If you decide to use this approach here are a couple of notes:
- Always write your code for prod and make changes on top of it. When you get local/staging versions as a starting point there is a bigger chance to publish something unwanted.
- Be as specific as possible when looking for the code subject of change.
- You probably want to use a utility like Lodash's get method when accessing items of the node.
- You may want to try your transformations in this Babel plugin playground. A lot cheaper than spinning your own locally.
Happy transforming 🦾