Skip to content

Instantly share code, notes, and snippets.

@ugultopu
Created January 31, 2021 12:54
Show Gist options
  • Save ugultopu/edf75585658b942f0c4b2d6f045dcd33 to your computer and use it in GitHub Desktop.
Save ugultopu/edf75585658b942f0c4b2d6f045dcd33 to your computer and use it in GitHub Desktop.

Summary

Specify the path to your plugin script with a leading ./ or ../. A Babel plugin is a regular CommonJS module, and Babel "processes" what you provide as the "plugin identifier" before converting it to a "module identifier" and loading that CommonJS module using the require function of Node.js.

Per Node.js module loading rules, a module identifier the does not start with ./ or ../ is searched in node_modules/. Since a plugin that we are currently developing is (most likely) not in a node_modules/ folder, we must specify the path to it with a leading ./ or ../ so that the plugin will not try to be located in a node_modules/ folder, but it will be tried to be located in the relative path that we provide.

Details

As mentioned in "What is a Babel Plugin?", a Babel plugin is nothing but regular a CommonJS module, whose value of module.exports is expected to be a function by Babel. Hence, after creating a Babel plugin (i.e after creating a CommonJS module whose value of module.exports is a function), we need to test it. How?

Well, we first need to install Babel. This tutorial will show the most straight-forward way of installing Babel, which is to install Babel CLI as a global npm package. You can install it another way if you like.

As mentioned in "Why does installing Babel CLI with NPM works, but not when installing with Yarn?", Babel CLI has a "peer dependency" (to Babel Core). Before version 3 and after version 7, npm installs missing peer dependencies by default but Yarn neither installs a missing peer dependency by default, nor issues a warning about a missing peer dependency. Hence, we will install Babel CLI as a global package using npm:

npm install --global @babel/cli

As a side note, the npm packages babel, babel-cli, babel-core are all legacy packages which are stuck at version 6. After version 6, Babel put everything under the npm organization name @babel/. Anything from the recent versions of Babel is under the @babel/ npm organization, which means anything from the recent versions that we want to install must be prefixed with @babel/. Hence, if you have such packages (babel, babel-cli, babel-core, etc. That is, any npm package that is published by Babel itself and starts with babel-), remove them using npm uninstall --global <package name> and install the modern counterparts of them (which are prefixed by @babel/).

Now Babel CLI is installed. To run it, we need to execute the babel command. Example:

$ babel --version
7.12.10 (@babel/core 7.12.10)

Now, we can give an example input JavaScript file to Babel to test what it does. Create the following file:

test.js

const exampleArrowFunction = () => 'hello';

and run Babel by providing that file:

$ babel test.js
const exampleArrowFunction = () => 'hello';

What happened? We ran Babel by providing an input file to it, Babel transformed it using the specified plugins on it and returned the result. Since we didn't specify any plugins, no changes has been made to the input file. Hence, the output is equal to the input.

Now let's provide a plugin to Babel. For example, there is the transform arrow functions plugin that is released by Babel itself. We can use this plugin to transform arrow functions into conventional, pre-ES6 functions.

We specify plugins in Babel CLI by putting them into the --plugins command line option. The full name of "transform arrow functions" is @babel/plugin-transform-arrow-functions. Hence:

$ babel test.js --plugins=@babel/plugin-transform-arrow-functions
Error: Cannot find module '@babel/plugin-transform-arrow-functions'
Require stack:
- /usr/local/lib/node_modules/@babel/core/lib/config/files/plugins.js
- /usr/local/lib/node_modules/@babel/core/lib/config/files/index.js
- /usr/local/lib/node_modules/@babel/core/lib/index.js
- /usr/local/lib/node_modules/@babel/cli/lib/babel/options.js
- /usr/local/lib/node_modules/@babel/cli/lib/babel/index.js
- /usr/local/lib/node_modules/@babel/cli/bin/babel.js
    at Function.Module._resolveFilename (node:internal/modules/cjs/loader:925:15)
    at resolve (node:internal/modules/cjs/helpers:98:19)
    at resolveStandardizedName (/usr/local/lib/node_modules/@babel/core/lib/config/files/plugins.js:101:7)
    at resolvePlugin (/usr/local/lib/node_modules/@babel/core/lib/config/files/plugins.js:42:10)
    at loadPlugin (/usr/local/lib/node_modules/@babel/core/lib/config/files/plugins.js:50:20)
    at createDescriptor (/usr/local/lib/node_modules/@babel/core/lib/config/config-descriptors.js:154:9)
    at /usr/local/lib/node_modules/@babel/core/lib/config/config-descriptors.js:109:50
    at Array.map (<anonymous>)
    at createDescriptors (/usr/local/lib/node_modules/@babel/core/lib/config/config-descriptors.js:109:29)
    at createPluginDescriptors (/usr/local/lib/node_modules/@babel/core/lib/config/config-descriptors.js:105:10) {
  code: 'MODULE_NOT_FOUND',
  requireStack: [
    '/usr/local/lib/node_modules/@babel/core/lib/config/files/plugins.js',
    '/usr/local/lib/node_modules/@babel/core/lib/config/files/index.js',
    '/usr/local/lib/node_modules/@babel/core/lib/index.js',
    '/usr/local/lib/node_modules/@babel/cli/lib/babel/options.js',
    '/usr/local/lib/node_modules/@babel/cli/lib/babel/index.js',
    '/usr/local/lib/node_modules/@babel/cli/bin/babel.js'
  ]
}
$

What happened? Well, we said that Babel plugins are CommonJS modules. We provided a plugin identifier to Babel, Babel processed the plugin identifier to a CommonJS module identifier (in this case, plugin identifier and CommonJS module identifier are the same, that is, no changes were performed on the plugin identifier by Babel) and tried to load a CommonJS module with the module identifier @babel/plugin-transform-arrow-functions. However, it couldn't load it because there is no such module in our computer! We first need to install it (Babel doesn't go and fetch a missing CommonJS module from the Internet. You have to install the required modules yourself before running Babel).

npm install @babel/plugin-transform-arrow-functions

After it has been successfully installed:

$ babel test.js --plugins=@babel/plugin-transform-arrow-functions
const exampleArrowFunction = function () {
  return 'hello';
};

Now it worked! With the help of @babel/plugin-transform-arrow-functions plugin, Babel transformed our arrow functions to conventional JavaScript function syntax.

One thing to note is, as explained in "What is a Babel Plugin?", Babel changes (processes, "mangles") plugin names that are in some forms before trying to locate them as CommonJS modules. For example, if we pass @babel/transform-arrow-functions as a plugin name, Babel will pass the CommonJS module identifier @babel/plugin-transform-arrow-functions to require. Hence, we can specify the plugin as @babel/transform-arrow-functions to Babel and it will work again. NOTE: This will work only with Babel, because Babel changes (processes, "mangles") plugin names that are in some forms before trying to locate them as CommonJS modules. When you are installing a Babel plugin as a package from npm, you need to give npm the plugin's full, proper name because unlike Babel, npm does not perform any name "processing".

$ babel test.js --plugins=@babel/transform-arrow-functions
const exampleArrowFunction = function () {
  return 'hello';
};

Again, it worked because Babel changed the name @babel/transform-arrow-functions to @babel/plugin-transform-arrow-functions before trying to locate it as a CommonJS module.

Okay, we have examined how to test a plugin that comes from the npm repository. How do we test our own plugin, which is most likely just a single JavaScript file in our computer, since a Babel plugin is nothing but a CommonJS module whose value of module.exports is expected to be a function by Babel? What we need to do is, we need to make sure that after Babel's "processing", the resulting CommonJS module identifier must point to our plugin. This means that the CommonJS module identifier must start with either ./ or ../ after processing. Because otherwise, as explained in Node.js modules API document, a CommonJS module is searched at node_modules folders, instead of being treated as a path to a module.

Hence, in order for a module identifier to be treated as a file system path, and not a node_modules/ path, it has to start with ./ or ../. We also indicated in "What is a Babel Plugin?" that Babel does not "process" the module identifiers that do not start with @ and that contain a slash.

Hence, to test a plugin that we are developing, all we need to do is we need to provide the path to that plugin, and that path must be a relative path that starts with ./ or ../. So, for example, if the plugin that we want to test is a file named my-plugin.js in this directory, we need to provide the path to it as ./my-plugin.js (or ./my-plugin), instead of just my-plugin.js (or just my-plugin). Because if we provided the path as my-plugin.js, first of all Babel will process it to babel-plugin-my-plugin.js. Hence, Babel will pass the CommonJS module identifier babel-plugin-my-plugin.js to require. Even if Babel didn't process it at all, Babel would pass my-plugin.js to require. This still would not work because a module identifier like this is searched in node_modules/ folders (as opposed to being treated as a path to a file), as explained in Node.js modules API document. Hence, if we provide the path to our plugin script (which is a CommonJS module) as a relative path that starts with ./ or ../, it will work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment