Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Codemod for Babel `import` into commonjs `require`

I'm looking for a way to converting a full node-project's Babelimports into CommonJS-style require(). The goal is to get rid of Babel.

Considering node.js has things like async/await built-in nowadays it feels redundant to run Babel. The only thing left that Babel does currently is that it converts the ES6-style imports into require().

I've been searching but can't find any elegant solution to do it semi-automatically. The output when compiling Babel isn't clean enough to just copy without a lot of manual work.

If I have a file with input like this:

import express from 'express'
import bodyParser from 'body-parser'
import authMiddleware from './middlewares/auth'
import { get } from 'lodash'

export const myVar = 1
export default function doSomething() {
  // ...
}

.. I'd want an output similar to this

const express = require('express')
const bodyParser = require('body-parser')
const authMiddleware = require('./middlewares/auth').default
const { get } = require('lodash')

export.myVar = 1
export.default = function doSomething() {
  // ...
}

Alternatively that it converted the files to the .mjs-syntax for the relative ones and used require() for external stuff.

It's not the first time I have an old node project running Babel where it's turned more-and-more redundant with time, so I'm sure someone has done neat solution to this before.

like image 757
Alex Avatar asked Mar 04 '23 03:03

Alex


2 Answers

I dig up the source code of babel-plugin-transform-modules-commonjs. Looks like it's impossible to config babel to output your desired result.

Reason behind is the necessity of helpers like _interopRequireDefault still holds strong, because ES module is not backward-compat to commonjs, notably the export default thing.

Take for example:

// input
import bodyParser from 'body-parser'
import authMiddleware from './middlewares/auth'

// your desired output
const bodyParser = require('body-parser') // <-- no default
const authMiddleware = require('./middlewares/auth').default // <-- default

// actual babel output
var _bodyParser = _interopRequireDefault(require("body-parser"));
var _auth = _interopRequireDefault(require("./middlewares/auth"));

You have no way to tell when to add .default and when not to. Only proper way to handle this is by wrapping require() with _interopRequireDefault and do runtime check.

If compiler does trace the required module and check if it's a ES module or commonjs module, then it can tell if .default is needed. However babel is designed around a single-file-at-a-time model, so no chance it can do that for you.

I think if you can figure out a reliable rule to tell when to add a .default and when not to then perhaps a simple regex-replace will solve your problem.


Side note. I do have some idea to hack it out with a customized babel plugin.

You can fork the babel-plugin-transform-modules-commonjs source, remove the _interopRequireDefault wrapping logic, then you use a resolver to do the aforementioned check-if-requiree-is-esmodule job, then see if .default is needed in output.

But easier said than done, this requires some serious effort.

like image 76
hackape Avatar answered Mar 16 '23 01:03

hackape


The simple solution, open your source code in an editor that can go through all files I use VSCode and set a ignore on the node_modules folder and do I regex replace on all files the full way if you need the multiple exports is below.

RegEx way

Search for:

import[\s*]([a-zA-Z0-9,]*)[\s*]from[\s*]['|"]([a-zA-Z0-9\{\},\.\/\\]*)['|"][\s*]

replace with

const $1 = require('$2')

if you use as do this also. Search for:

import[\s*][a-zA-Z0-9,]*[\s*]as[\s*]([a-zA-Z0-9]*)[\s*]from[\s*]['|"]([a-zA-Z0-9\{\},\.\/\\]*)['|"][\s*]

replace with

const $1 = require('$2')

there are some drawbacks here you can't use multiple exports for that you need The full way

Long way

Ok so for anyone who is interested, here is the process I used to work this out, you can then copy your source out of the build folder into a new location as a new source or overwrite your old src and the remove all of babel from your project (npm prune).

This will leave all the support stuff that module needs including the support for export default _interopRequireDefault() the only way to get rid of this is to create your own plugin that does not do this.

Step 1

Identify what ECMA babel was using for that. So I went to https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import

it shows that import has been part of the spec since ES6 (also known as ECMA2015)

Step 2

The presets are just groups of packages so identify the package for that particular transpile.

Opened my package.json and looked for babel-preset-es2015 found it. went to node_moduels\babel-preset-es2015, opened it's package.json to find

    "babel-plugin-transform-es2015-modules-amd": "^6.24.1",
    "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1",
    "babel-plugin-transform-es2015-modules-systemjs": "^6.24.1",
    "babel-plugin-transform-es2015-modules-umd": "^6.24.1",

Step 3

Some testing so using --plugins= argument for babel I tested what each of them did on a small set of 2 files one requiring the other to and test each one I worked out it was the commonjs version that was needed for require();

Step 4

Do the conversion

So make sure you have the following node modules installed babel-cli, babel-core, babel-plugin-transform-es2015-modules-commonjs

Then fire up the CLI and do,

babel --plugins=transform-es2015-modules-commonjs ./src/ --out-dir build/

taken from https://babeljs.io/docs/en/babel-cli

like image 20
Barkermn01 Avatar answered Mar 16 '23 01:03

Barkermn01