Skip to content

Instantly share code, notes, and snippets.

@ugultopu
Last active January 31, 2021 00:30
Show Gist options
  • Save ugultopu/2d69b41d654abc89cb045cdeb83f56e9 to your computer and use it in GitHub Desktop.
Save ugultopu/2d69b41d654abc89cb045cdeb83f56e9 to your computer and use it in GitHub Desktop.

Disclaimer: This article applies to Babel that runs on Node.js, which is the most common way of running Babel. I didn't check how Babel on a web browser works. The reason I wrote this article is because I didn't notice anywhere on Babel docs that explains what exactly is a Babel plugin. Babel docs mention how a Babel plugin works, what's its purpose, etc. but as far as I can tell, they don't mention what a Babel plugin is.

A Babel plugin is a CommonJS module, whose export is expected by Babel to be a function. That's it. Note that I didn't say "default export", because there is no concept of "default export" in CommonJS. In CommonJS, there is only the module.exports property. This property's value can be any valid JavaScript value. However, Babel expects the value of the module.exports property of a CommonJS module that is designated as a Babel plugin to be a JavaScript function.

How do we know that a Babel plugin is a "CommonJS module"? Can't it be an "ES6 module" as well? No. A Babel plugin is a CommonJS module. It cannot be an ES6 module or any other type of module, at least as of Babel version 7.12.10. The reason is, Babel is using Node.js' require function to load modules. In babel-core/src/config/files/plugins.js#loadPlugin, it first resolves the plugin and then makes a call to requireModule. In turn, requireModule calls Node.js require function with its argument and returns the results of the require function call.

So, we understand that a Babel plugin is just a required module. How does this make Babel plugins being only CommonJS modules, and not any other types of modules (such as ES6 modules)? The reason is, require function always assumes that the module it loads is a CommonJS module. It does not try to detect the module type and use another loading mechanism to load other types of modules. The only check it does is the following rudimentary check: If the argument (which is a string) provided to require ends with .mjs, require throws an ERR_REQUIRE_ESM error, which says that ECMAScript modules cannot be required, they must be imported. This behavior is also documented. Otherwise, require proceeds with assuming that the module pointed to by its argument is a CommonJS module. Hence, if the module is not a CommonJS module, errors will occur while require processes the file and require will throw these errors. Hence, we understand that a Babel plugin is simply a CommonJS module.

One thing to note is that Babel mangles what you provide before passing them to require. The purpose of this is to allow the user to specify plugin/preset names as "shorthands". Some examples are as follows:

  • If you pass asdf as a plugin name to Babel, Babel will not pass asdf to the require call. It will pass babel-plugin-asdf to the require call.

  • If you pass @babel/asdf as a plugin name to Babel, Babel will pass @babel/plugin-asdf to require.

  • If you pass @someOrgName/asdf as as plugin name to Babel, Babel will pass @someOrgName/babel-plugin-asdf to require. The difference between a non-@babel organization name and the @babel organization name is the additional babel- that comes after the slash for modules in non-@babel organization names.

  • If you pass @someOrgName as a plugin name to Babel, Babel will pass @someOrgName/babel-plugin to require. I think this is used to designate a organization-wide Babel plugin. Note that there is no counterpart of this with the @babel organization. That is, if you pass just @babel as a plugin name to Babel, Babel will not pass @babel/plugin or @babel/babel-plugin to require. It will just pass @babel. @babel is an invalid npm package name, since it does not have a package name, it only has an organization name. Hence, it will not work. Also, as a side note, there are no npm packages named @babel/plugin or @babel/babel-plugin.

  • If you don't want any mangling to the modules, prefix them with module:. That is, if you pass module:<anything> as a plugin name to Babel, Babel will just remove the leading module: from it and pass the remaining part of the string as is to require. Likewise, even if the plugin name that you pass to Babel does not start with module:, but it contains a slash (/) and does not start with @, again, Babel will not perform any mangling on the name and pass it as is to require.

To rephrase, we can say that what we provide to Babel are regarded as "plugin identifiers". Babel makes some mangling (processing) to these "plugin identifiers" and turns them into "CommonJS module identifiers" and then passes them to the require function.

Since it is hard to precisely describe all rules of converting a "plugin identifier" to a "CommonJS module identifier" in writing, you can go to babel-core/src/config/files/plugins.js to understand how Babel converts a "plugin identifier" to a "CommonJS module identifier". The following is a non-exhaustive list of examples of how Babel converts a "plugin identifier" to a "CommonJS module identifier":

  • asdf -> babel-plugin-asdf

  • @babel/asdf -> @babel/plugin-asdf

  • @zxcv/asdf -> @zxcv/babel-plugin-asdf

  • @zxcv -> @zxcv/babel-plugin

  • module:asdf/ghjk -> asdf/ghjk

  • asdf/ghjl -> asdf/ghjl

  • babel-plugin-asdf -> babel-plugin-asdf

  • babel-plugin-asdf/ghjk -> babel-plugin-asdf/ghjk

  • babel-preset-asdf -> babel-preset-asdf

  • babel-preset-asdf/ghjk -> babel-preset-asdf/ghjk

  • @babel/asdf/ghjk -> @babel/asdf/ghjk

  • @babel/plugin-asdf -> @babel/plugin-asdf

  • @babel/plugin-asdf/ghjk -> @babel/plugin-asdf/ghjk

  • @babel/preset-asdf -> @babel/preset-asdf

  • @babel/preset-asdf/ghjk -> @babel/preset-asdf/ghjk

  • etc.

After a "CommonJS module identifier" is passed to require, the rest of the algorithm belongs to Node.js. You can read the linked document to understand which module will be loaded given a module identifier.

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