Search code examples
javascriptbabeljsbabel-plugin

How to write a babel js plugin that passes output to other plugins?


I'm trying to write a babel plugin that prepends/appends content to a file - for example; add the lines console.log("start of " + __filename); and console.log("end of " + __filename); to each file.

So far, I've managed to write a visitor that does exactly this, however the existing code doesn't get modified by any other plugins before OR after my plugin operates.

For example, I have the following file:

import * as foo from 'foo';
import * as bar from 'bar';

console.dir({ foo, bar });

Using the env preset alone (ie. without my plugin) and the option targets.node: 'current' I end up with the output - note that es6 imports have been transformed into commonjs requires:

'use strict';
var _foo = require('foo');
var foo = _interopRequireWildcard(_foo);
var _bar = require('bar');
var bar = _interopRequireWildcard(_bar);
function _interopRequireWildcard(obj) { /* resolve commonjs or es6 module */ }
console.dir({ foo, bar });

However as soon as I add my own plugin to this; it appears that the env preset is skipped in favour of my own plugin - however I'd like both plugins to be applied (mine first, preferably).


So far, my plugin code looks like:

module.exports = function wrapModule(babel) {

  const prepend = transform(`console.log("start of " + __filename);`)
  const append = transform(`console.log("end of " + __filename);`)
  return {
    visitor: {
      Program(path, {opts}) {
        path.replaceWith(t.program([...prepend, ...path.node.body, ...append]))
        path.stop()
      }
    }
  }

  function transform(content) {
    return babel.transform(content).ast.program.body
  }
}

and my .babelrc is simply:

{
  "presets": [[
    "env", { "targets": { "node": "current" } }
  ]],
  "plugins": [
    "./wrapper-babel-plugin"
  ]
}

And this is producing the output:

console.log("start of " + __filename);
import * as foo from 'foo';
import * as bar from 'bar';

console.dir({ foo, bar });
console.log("end of " + __filename);

Can anyone suggest what I'm missing to have babel chain my plugin with other plugins, to allow me to use multiple plugins in combination?


Solution

  • And this is producing the output:

    Given that you are using eval, and you are calling your transform function with the wrong number of arguments, that can't possibly be true :)

    The correct way to write what you're looking for would be

    export default function wrapModule({ template }) {
      const prepend = template(`console.log("start of " + __filename);`);
      const append = template(`console.log("end of " + __filename);`);
    
      return {
        visitor: {
          Program(path, {opts}) {
            path.unshiftContainer("body", prepend());
            path.pushContainer("body", append());
          }
        }
      };
    }
    

    By using unshiftContainer and pushContainer Babel can queue up those nodes for processing by other plugins. This also uses template to generate the ASTs for your two snippets.