4

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?

Micheal Hill
  • 1,619
  • 2
  • 17
  • 38

1 Answers1

7

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.

loganfsmyth
  • 156,129
  • 30
  • 331
  • 251
  • Ah, thanks! I trimmed this down for the example, and seems I missed a few bits. The unshiftContainer/pushContainer is indeed what I was missing. – Micheal Hill Feb 04 '18 at 22:25
  • For the benefit of any future readers who are confused about the transform function mentioned above; I've edited the question to correct the function. – Micheal Hill Feb 04 '18 at 22:31