asapach / babel-plugin-rewire-exports

Babel plugin for stubbing [ES6, ES2015] module exports
MIT License
66 stars 7 forks source link
babel babel-plugin es2015 es6 esm javascript rewire rollup stub systemjs unit-testing webpack

babel-plugin-rewire-exports

NPM Version NPM Downloads Build Status

Babel plugin for stubbing (ES6, ES2015) module exports. It allows to rewire the exported values in all the importing modules. Unlike babel-plugin-rewire it doesn't modify the module internals (e.g. imports and top-level variables and functions). See How it works section for implementation details.

Exports

Plugin transforms module exports in such a way that they can be stubbed (or "rewired") via the following API:

Example

Named export:

//------ text.js ------
export let message = 'Hello world!'

//------ logger.js ------
import {message} from './text.js'

export default function () {
  console.log(message)
}

//------ main.js ------
import {rewire$message, restore} from './text.js'
import logger from './logger.js'

logger() // 'Hello world!'
rewire$message('I am now rewired')
logger() // 'I am now rewired'
restore()
logger() // 'Hello world!'

Default export:

//------ fetch.js ------
export default function (url) {
  // perform some expensive remote call
}

//------ adapter.js ------
import fetch from './fetch.js'

export function fetchItems() {
  return fetch('/items')
}

//------ test.js ------
import {rewire, restore} from './fetch.js'
import {fetchItems} from './adapter.js'

// Jasmine example
describe('adapter', function () {
  beforeEach(function () {
    rewire(this.spy = jasmine.createSpy('fetch'))
  })
  afterAll(restore)

  it('should call fetch', function () {
    fetchItems()
    expect(this.spy).toHaveBeenCalledWith('/items')
  })
})

// Mocha/Chai and Sinon example
describe('adapter', function () {
  var spy

  beforeEach(function () {
    rewire(spy = sinon.spy())
  })
  after(restore)

  it('should call fetch', function () {
    fetchItems()
    expect(spy.withArgs('/items').calledOnce).to.be.true
  })
})

Compatibility

How it works

In ES6, imports are live read-only views on exported values:

//------ lib.js ------
export let counter = 3;
export function incCounter() {
    counter++;
}

//------ main1.js ------
import { counter, incCounter } from './lib';

// The imported value `counter` is live
console.log(counter); // 3
incCounter();
console.log(counter); // 4

// The imported value can’t be changed
counter++; // TypeError

This allows for any exports to be overwritten from within the module - and imports will be automatically updated via their bindings.

Transformations

Here's how various kinds of export declarations are transformed:

Installation

$ npm install babel-plugin-rewire-exports

Usage

Via .babelrc (Recommended)

.babelrc

// without options
{
  "plugins": ["rewire-exports"]
}

// with options
{
  "plugins": [
    ["rewire-exports", {
      "unsafeConst": true
    }]
  ]
}

Via CLI

$ babel --plugins rewire-exports script.js

Via Node API

require("@babel/core").transform("code", {
  plugins: ["rewire-exports"]
});

Options

unsafeConst

boolean, defaults to false.

Constants cannot be rewired if you're targeting ES2015+, because the plugin relies on variables being assign-able in order to work. However setting unsafeConst: true will convert export const foo = 'bar' to export let foo = 'bar'. This will allow to treat constant exports as regular variables. This is potentially unsafe if your code relies on constants being read-only.