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.
Plugin transforms module exports in such a way that they can be stubbed (or "rewired") via the following API:
- default export - plugin exports additional
rewire(stub)
function that allows to replace the original - named exports - for each export (e.g.
export var foo
) an additional functionrewire$foo(stub)
is exported restore()
function allows to restore the exports to their original values- if there are existing
rewire
orrestore
top-level identifiers, the generated exports will be namedrewire$default
andrestore$rewire
respectively
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
})
})
- β€οΈ Works with modern browsers:
- π Safari 10.1+
- π Chrome 61+
- π¦ Firefox 60+
- π Edge 16+
- π Node:
@std/esm
8.5.0+
behind--experimental-modules
flag
- β¨ SystemJS
- π Webpack 2+
- π₯ Rollup
- π¦ Works with CommonJS, including running in Node and bundling with webpack/browserify:
- Use
env
preset - When specifying
plugins
directly make sure that"rewire-exports"
goes before"@babel/plugin-transform-modules-commonjs"
- Works with π Jest using babel-jest
- Use
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.
Here's how various kinds of export declarations are transformed:
-
Literals (
export default 'foo'
) - the original value is copied to a variable to be stubbed and restored later:export {_default as default}
-
Variables:
- named exports (
export var foo
,export let bar
orexport {baz}
) are left intact, but their initial values are similarly copied to temp variables. - default export (
export default foo
) is converted to a named export to enable live binding:export {foo as default}
- named exports (
-
Constants (
export const foo = 'bar'
orexport default foo
) are treated similar to variables, but their values are not modified within the module (since they are read-only) - only exported values are rewired:- named exports:
export { _foo as foo }
- default export:
export { _default as default }
You can use
unsafeConst
option to convertconst
tolet
in order to enable live binding. - named exports:
-
Functions (
export default function () {β¦}
orexport function foo() {β¦}
) are converted into a function expression and exported variable by the same name. The variable initialization is hoisted to the top of the scope to preserve existing behavior. -
Classes (
export default class foo {β¦}
orexport class foo {β¦}
) are handled similarly to functions except the variables are not hoisted (again to preserve the existing behavior). -
Re-exports (
export * from './foo.js'
orexport {bar} from 'baz'
) are ignored. They can be rewired in the original modules. -
Immutable values such as
undefined
, globals and imports are copied similar to literals.
$ npm install babel-plugin-rewire-exports
.babelrc
// without options
{
"plugins": ["rewire-exports"]
}
// with options
{
"plugins": [
["rewire-exports", {
"unsafeConst": true
}]
]
}
$ babel --plugins rewire-exports script.js
require("@babel/core").transform("code", {
plugins: ["rewire-exports"]
});
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.